web-dev-qa-db-fra.com

Comment implémenter RouteReuseStrategy devraitDétacher pour des itinéraires spécifiques dans Angular 2

J'ai un module Angular 2 dans lequel j'ai implémenté le routage et je souhaite que les états soient stockés lors de la navigation. L'utilisateur doit pouvoir: 1. rechercher des documents à l'aide d'une formule de recherche 2. accéder à l'un des résultats 3. accéder à nouveau au résultat de la recherche - sans communiquer avec le serveur

C'est possible, y compris RouteReuseStrategy. La question qui se pose est la suivante: comment implémenter que le document ne soit pas stocké?

Donc, l'état de "documents" du chemin de route doit être stocké et l'état de "documents /: id" du chemin de route NE DOIT PAS être stocké?

83
Anders Gram Mygind

Hey Anders, bonne question!

J'ai presque le même cas d'utilisation que vous et je voulais faire la même chose! Recherche d'utilisateur> Obtenir des résultats> L'utilisateur navigue jusqu'au résultat> L'utilisateur revient en arrière> BOOMretour rapide aux résultats, mais vous ne souhaitez pas stocker le résultat spécifique auquel l'utilisateur a accédé.

tl; dr

Vous devez avoir une classe qui implémente RouteReuseStrategy et fournissez votre stratégie dans le ngModule. Si vous souhaitez modifier le moment où la route est enregistrée, modifiez la fonction shouldDetach. Lorsqu'il renvoie true, Angular enregistre l'itinéraire. Si vous souhaitez modifier la connexion de la route, modifiez la fonction shouldAttach. Lorsque shouldAttach renvoie la valeur true, Angular utilisera la route stockée à la place de la route demandée. Voici un Plunker avec lequel vous pourrez jouer.

À propos de RouteReuseStrategy

En posant cette question, vous comprenez déjà que RouteReuseStrategy vous permet de dire à Angular not ​​de détruire un composant, mais en fait de le sauvegarder pour le restituer ultérieurement. C'est cool parce que cela permet:

  • Diminué appels du serveur
  • Augmenté vitesse
  • ET le composant restitue, par défaut, dans le même état

Ce dernier point est important si vous souhaitez, par exemple, laisser une page temporairement, même si l’utilisateur a saisi un lot de texte. Les applications d'entreprise vont adorer cette fonctionnalité en raison de la quantité excessive de formulaires!

C'est ce que je suis venu avec pour résoudre le problème. Comme vous l'avez dit, vous devez utiliser la RouteReuseStrategy proposée par @ angular/router dans les versions 3.4.1 et supérieures.

TODO

Premier Assurez-vous que votre projet a la version @ angular/router 3.4.1 ou supérieure.

Suivant , créez un fichier qui hébergera votre classe qui implémentera RouteReuseStrategy. J'ai appelé le mien reuse-strategy.ts et l'ai placé dans le dossier /app par mesure de sécurité. Pour le moment, cette classe devrait ressembler à:

import { RouteReuseStrategy } from '@angular/router';

export class CustomReuseStrategy implements RouteReuseStrategy {
}

(ne vous inquiétez pas de vos erreurs TypeScript, nous sommes sur le point de tout résoudre)

Terminez le travail préparatoire en fournissant la classe à votre app.module. Notez que vous n'avez pas encore écrit CustomReuseStrategy, mais que vous devriez continuer et import le de reuse-strategy.ts tout de même. Aussi import { RouteReuseStrategy } from '@angular/router';

@NgModule({
    [...],
    providers: [
        {provide: RouteReuseStrategy, useClass: CustomReuseStrategy}
    ]
)}
export class AppModule {
}

La dernière pièce est en train d'écrire la classe qui contrôlera si les itinéraires sont détachés, stockés, récupérés et rattachés. Avant d’arriver à l’ancien copier/coller, je vais faire une brève explication de la mécanique ici, telle que je la comprends. Référencez le code ci-dessous pour les méthodes que je décris, et bien sûr, il y a beaucoup de documentation dans le code .

  1. Lorsque vous naviguez, shouldReuseRoute se déclenche. Celle-ci est un peu étrange pour moi, mais si elle renvoie true, elle réutilise en fait la route sur laquelle vous êtes actuellement et aucune des autres méthodes n'est déclenchée. Je retourne simplement false si l'utilisateur est en train de naviguer.
  2. Si shouldReuseRoute renvoie false, shouldDetach se déclenche. shouldDetach détermine si vous souhaitez ou non enregistrer l'itinéraire et renvoie un boolean indiquant ce nombre. C'est ici que vous devriez décider de stocker/ne pas stocker les chemins , ce que je ferais en vérifiant un tableau de chemins que vous voulez = stocké dans route.routeConfig.path, et renvoie false si la variable path n'existe pas dans le tableau.
  3. Si shouldDetach renvoie true, store est déclenché, ce qui vous permet de stocker les informations de votre choix sur l'itinéraire. Quoi que vous fassiez, vous devrez stocker la DetachedRouteHandle car c’est ce que Angular utilise pour identifier ultérieurement votre composant stocké. Ci-dessous, je stocke les variables DetachedRouteHandle et ActivatedRouteSnapshot dans une variable locale de ma classe.

Donc, nous avons vu la logique de stockage, mais qu'en est-il de la navigation to d'un composant? Comment Angular décide-t-il d'intercepter votre navigation et de mettre celle stockée à sa place?

  1. Encore une fois, après que shouldReuseRoute soit retourné, false, shouldAttach s'exécute, ce qui vous permet de déterminer si vous souhaitez régénérer ou utiliser le composant en mémoire. Si vous souhaitez réutiliser un composant stocké, retournez true et vous êtes sur la bonne voie!
  2. Maintenant, Angular vous demandera "quel composant voulez-vous que nous utilisions?", Ce que vous indiquerez en renvoyant le DetachedRouteHandle de ce composant sur retrieve.

C'est à peu près toute la logique dont vous avez besoin! Dans le code de reuse-strategy.ts, ci-dessous, je vous ai également laissé une fonction astucieuse qui comparera deux objets. Je l'utilise pour comparer les route.params et route.queryParams de la route future avec ceux stockés. Si ceux-ci correspondent tous, je veux utiliser le composant stocké au lieu d'en générer un nouveau. Mais comment vous le faites c'est à vous!

reuse-strategy.ts

/**
 * reuse-strategy.ts
 * by corbfon 1/6/17
 */

import { ActivatedRouteSnapshot, RouteReuseStrategy, DetachedRouteHandle } from '@angular/router';

/** Interface for object which can store both: 
 * An ActivatedRouteSnapshot, which is useful for determining whether or not you should attach a route (see this.shouldAttach)
 * A DetachedRouteHandle, which is offered up by this.retrieve, in the case that you do want to attach the stored route
 */
interface RouteStorageObject {
    snapshot: ActivatedRouteSnapshot;
    handle: DetachedRouteHandle;
}

export class CustomReuseStrategy implements RouteReuseStrategy {

    /** 
     * Object which will store RouteStorageObjects indexed by keys
     * The keys will all be a path (as in route.routeConfig.path)
     * This allows us to see if we've got a route stored for the requested path
     */
    storedRoutes: { [key: string]: RouteStorageObject } = {};

    /** 
     * Decides when the route should be stored
     * If the route should be stored, I believe the boolean is indicating to a controller whether or not to fire this.store
     * _When_ it is called though does not particularly matter, just know that this determines whether or not we store the route
     * An idea of what to do here: check the route.routeConfig.path to see if it is a path you would like to store
     * @param route This is, at least as I understand it, the route that the user is currently on, and we would like to know if we want to store it
     * @returns boolean indicating that we want to (true) or do not want to (false) store that route
     */
    shouldDetach(route: ActivatedRouteSnapshot): boolean {
        let detach: boolean = true;
        console.log("detaching", route, "return: ", detach);
        return detach;
    }

    /**
     * Constructs object of type `RouteStorageObject` to store, and then stores it for later attachment
     * @param route This is stored for later comparison to requested routes, see `this.shouldAttach`
     * @param handle Later to be retrieved by this.retrieve, and offered up to whatever controller is using this class
     */
    store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
        let storedRoute: RouteStorageObject = {
            snapshot: route,
            handle: handle
        };

        console.log( "store:", storedRoute, "into: ", this.storedRoutes );
        // routes are stored by path - the key is the path name, and the handle is stored under it so that you can only ever have one object stored for a single path
        this.storedRoutes[route.routeConfig.path] = storedRoute;
    }

    /**
     * Determines whether or not there is a stored route and, if there is, whether or not it should be rendered in place of requested route
     * @param route The route the user requested
     * @returns boolean indicating whether or not to render the stored route
     */
    shouldAttach(route: ActivatedRouteSnapshot): boolean {

        // this will be true if the route has been stored before
        let canAttach: boolean = !!route.routeConfig && !!this.storedRoutes[route.routeConfig.path];

        // this decides whether the route already stored should be rendered in place of the requested route, and is the return value
        // at this point we already know that the paths match because the storedResults key is the route.routeConfig.path
        // so, if the route.params and route.queryParams also match, then we should reuse the component
        if (canAttach) {
            let willAttach: boolean = true;
            console.log("param comparison:");
            console.log(this.compareObjects(route.params, this.storedRoutes[route.routeConfig.path].snapshot.params));
            console.log("query param comparison");
            console.log(this.compareObjects(route.queryParams, this.storedRoutes[route.routeConfig.path].snapshot.queryParams));

            let paramsMatch: boolean = this.compareObjects(route.params, this.storedRoutes[route.routeConfig.path].snapshot.params);
            let queryParamsMatch: boolean = this.compareObjects(route.queryParams, this.storedRoutes[route.routeConfig.path].snapshot.queryParams);

            console.log("deciding to attach...", route, "does it match?", this.storedRoutes[route.routeConfig.path].snapshot, "return: ", paramsMatch && queryParamsMatch);
            return paramsMatch && queryParamsMatch;
        } else {
            return false;
        }
    }

    /** 
     * Finds the locally stored instance of the requested route, if it exists, and returns it
     * @param route New route the user has requested
     * @returns DetachedRouteHandle object which can be used to render the component
     */
    retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {

        // return null if the path does not have a routerConfig OR if there is no stored route for that routerConfig
        if (!route.routeConfig || !this.storedRoutes[route.routeConfig.path]) return null;
        console.log("retrieving", "return: ", this.storedRoutes[route.routeConfig.path]);

        /** returns handle when the route.routeConfig.path is already stored */
        return this.storedRoutes[route.routeConfig.path].handle;
    }

    /** 
     * Determines whether or not the current route should be reused
     * @param future The route the user is going to, as triggered by the router
     * @param curr The route the user is currently on
     * @returns boolean basically indicating true if the user intends to leave the current route
     */
    shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
        console.log("deciding to reuse", "future", future.routeConfig, "current", curr.routeConfig, "return: ", future.routeConfig === curr.routeConfig);
        return future.routeConfig === curr.routeConfig;
    }

    /** 
     * This nasty bugger finds out whether the objects are _traditionally_ equal to each other, like you might assume someone else would have put this function in Vanilla JS already
     * One thing to note is that it uses coercive comparison (==) on properties which both objects have, not strict comparison (===)
     * Another important note is that the method only tells you if `compare` has all equal parameters to `base`, not the other way around
     * @param base The base object which you would like to compare another object to
     * @param compare The object to compare to base
     * @returns boolean indicating whether or not the objects have all the same properties and those properties are ==
     */
    private compareObjects(base: any, compare: any): boolean {

        // loop through all properties in base object
        for (let baseProperty in base) {

            // determine if comparrison object has that property, if not: return false
            if (compare.hasOwnProperty(baseProperty)) {
                switch(typeof base[baseProperty]) {
                    // if one is object and other is not: return false
                    // if they are both objects, recursively call this comparison function
                    case 'object':
                        if ( typeof compare[baseProperty] !== 'object' || !this.compareObjects(base[baseProperty], compare[baseProperty]) ) { return false; } break;
                    // if one is function and other is not: return false
                    // if both are functions, compare function.toString() results
                    case 'function':
                        if ( typeof compare[baseProperty] !== 'function' || base[baseProperty].toString() !== compare[baseProperty].toString() ) { return false; } break;
                    // otherwise, see if they are equal using coercive comparison
                    default:
                        if ( base[baseProperty] != compare[baseProperty] ) { return false; }
                }
            } else {
                return false;
            }
        }

        // returns true only after false HAS NOT BEEN returned through all loops
        return true;
    }
}

Comportement

Cette implémentation stocke chaque itinéraire unique que l'utilisateur visite sur le routeur exactement une fois. Cela continuera à s'ajouter aux composants stockés en mémoire tout au long de la session de l'utilisateur sur le site. Si vous souhaitez limiter les itinéraires que vous enregistrez, vous devez utiliser la méthode shouldDetach. Il contrôle les itinéraires que vous enregistrez.

Exemple

Supposons que votre utilisateur cherche quelque chose dans la page d'accueil, ce qui les dirige vers le chemin search/:term, qui pourrait ressembler à www.yourwebsite.com/search/thingsearchedfor. La page de recherche contient un tas de résultats de recherche. Vous souhaitez stocker cette route, au cas où ils voudraient y revenir! Maintenant, ils cliquent sur un résultat de recherche et accèdent à view/:resultId, que vous ne voulez pas voulez stocker, étant donné qu'ils ne seront probablement là qu'une seule fois. Avec la mise en œuvre ci-dessus en place, je voudrais simplement changer la méthode shouldDetach! Voici à quoi cela pourrait ressembler:

First off Faisons un tableau des chemins que nous voulons stocker.

private acceptedRoutes: string[] = ["search/:term"];

maintenant, dans shouldDetach nous pouvons comparer le route.routeConfig.path à notre tableau.

shouldDetach(route: ActivatedRouteSnapshot): boolean {
    // check to see if the route's path is in our acceptedRoutes array
    if (this.acceptedRoutes.indexOf(route.routeConfig.path) > -1) {
        console.log("detaching", route);
        return true;
    } else {
        return false; // will be "view/:resultId" when user navigates to result
    }
}

Parce que Angular va ne stocke qu'une instance d'une route, ce stockage sera léger et nous ne stockons que le composant situé à search/:term et pas tous les autres!

Liens supplémentaires

Bien qu'il n'y ait pas encore beaucoup de documentation, voici quelques liens vers ce qui existe:

Documents angulaires: https://angular.io/docs/ts/latest/api/router/index/RouteReuseStrategy-class.html

Intro Article: https://www.softwarearchitekt.at/post/2016/12/02/sticky-routes-in-angular-2-3-with-routereusestrategy.aspx

160
Corbfon

Ne soyez pas intimidé par la réponse acceptée, ceci est assez simple. Voici une réponse rapide à ce dont vous avez besoin. Je recommanderais au moins de lire la réponse acceptée, car elle est très détaillée.

Cette solution ne fait aucune comparaison de paramètres comme la réponse acceptée, mais elle fonctionnera bien pour stocker un ensemble de routes.

app.module.ts importations:

import { RouteReuseStrategy } from '@angular/router';
import { CustomReuseStrategy, Routing } from './shared/routing';

@NgModule({
//...
providers: [
    { provide: RouteReuseStrategy, useClass: CustomReuseStrategy },
  ]})

shared/routing.ts:

export class CustomReuseStrategy implements RouteReuseStrategy {
 routesToCache: string[] = ["dashboard"];
 storedRouteHandles = new Map<string, DetachedRouteHandle>();

 // Decides if the route should be stored
 shouldDetach(route: ActivatedRouteSnapshot): boolean {
    return this.routesToCache.indexOf(route.routeConfig.path) > -1;
 }

 //Store the information for the route we're destructing
 store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
    this.storedRouteHandles.set(route.routeConfig.path, handle);
 }

//Return true if we have a stored route object for the next route
 shouldAttach(route: ActivatedRouteSnapshot): boolean {
    return this.storedRouteHandles.has(route.routeConfig.path);
 }

 //If we returned true in shouldAttach(), now return the actual route data for restoration
 retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
    return this.storedRouteHandles.get(route.routeConfig.path);
 }

 //Reuse the route if we're going to and from the same route
 shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    return future.routeConfig === curr.routeConfig;
 }
}
30
Chris Fremgen

Pour utiliser la stratégie de Chris Fremgen avec des modules chargés paresseusement, modifiez la classe CustomReuseStrategy comme suit:

import {ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy} from '@angular/router';

export class CustomReuseStrategy implements RouteReuseStrategy {
  routesToCache: string[] = ["company"];
  storedRouteHandles = new Map<string, DetachedRouteHandle>();

  // Decides if the route should be stored
  shouldDetach(route: ActivatedRouteSnapshot): boolean {
     return this.routesToCache.indexOf(route.data["key"]) > -1;
  }

  //Store the information for the route we're destructing
  store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
     this.storedRouteHandles.set(route.data["key"], handle);
  }

  //Return true if we have a stored route object for the next route
  shouldAttach(route: ActivatedRouteSnapshot): boolean {
     return this.storedRouteHandles.has(route.data["key"]);
  }

  //If we returned true in shouldAttach(), now return the actual route data for restoration
  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
     return this.storedRouteHandles.get(route.data["key"]);
  }

  //Reuse the route if we're going to and from the same route
  shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
     return future.routeConfig === curr.routeConfig;
  }
}

enfin, dans les fichiers de routage de vos modules de fonctionnalités, définissez vos clés:

{ path: '', component: CompanyComponent, children: [
    {path: '', component: CompanyListComponent, data: {key: "company"}},
    {path: ':companyID', component: CompanyDetailComponent},
]}

Plus d'infos ici .

10
Uğur Dinç

En plus de la réponse acceptée (par Corbfon) et de l'explication plus courte et plus simple de Chris Fremgen, je souhaite ajouter un moyen plus souple de gérer les itinéraires qui devraient utiliser la stratégie de réutilisation.

Les deux réponses stockent les routes que nous voulons mettre en cache dans un tableau, puis vérifient si le chemin de route actuel est dans le tableau ou non. Cette vérification est effectuée dans la méthode shouldDetach.

Je trouve cette approche inflexible car si nous voulons changer le nom de la route, nous devons nous rappeler de changer également le nom de la route dans notre classe CustomReuseStrategy. Nous pouvons soit oublier de le changer, soit un autre développeur de notre équipe peut décider de changer le nom de la route sans même savoir l'existence de RouteReuseStrategy.

Au lieu de stocker les routes que nous voulons mettre en cache dans un tableau, nous pouvons les marquer directement dans RouterModule à l'aide de l'objet data. De cette façon, même si nous changions le nom de la route, la stratégie de réutilisation serait toujours appliquée.

{
  path: 'route-name-i-can-change',
  component: TestComponent,
  data: {
    reuseRoute: true
  }
}

Et ensuite, dans la méthode shouldDetach, nous en faisons usage.

shouldDetach(route: ActivatedRouteSnapshot): boolean {
  return route.data.reuseRoute === true;
}
5
Davor

ce qui suit est un travail! référence: https://www.cnblogs.com/lovesangel/p/7853364.html

import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router';

export class CustomReuseStrategy implements RouteReuseStrategy {

    public static handlers: { [key: string]: DetachedRouteHandle } = {}

    private static waitDelete: string

    public static deleteRouteSnapshot(name: string): void {
        if (CustomReuseStrategy.handlers[name]) {
            delete CustomReuseStrategy.handlers[name];
        } else {
            CustomReuseStrategy.waitDelete = name;
        }
    }
   
    public shouldDetach(route: ActivatedRouteSnapshot): boolean {
        return true;
    }

   
    public store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
        if (CustomReuseStrategy.waitDelete && CustomReuseStrategy.waitDelete == this.getRouteUrl(route)) {
            // 如果待删除是当前路由则不存储快照
            CustomReuseStrategy.waitDelete = null
            return;
        }
        CustomReuseStrategy.handlers[this.getRouteUrl(route)] = handle
    }

    
    public shouldAttach(route: ActivatedRouteSnapshot): boolean {
        return !!CustomReuseStrategy.handlers[this.getRouteUrl(route)]
    }

    /** 从缓存中获取快照,若无则返回nul */
    public retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
        if (!route.routeConfig) {
            return null
        }

        return CustomReuseStrategy.handlers[this.getRouteUrl(route)]
    }

   
    public shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
        return future.routeConfig === curr.routeConfig &&
            JSON.stringify(future.params) === JSON.stringify(curr.params);
    }

    private getRouteUrl(route: ActivatedRouteSnapshot) {
        return route['_routerState'].url.replace(/\//g, '_')
    }
}
4
红兵伍

Une autre implémentation plus valide, complète et réutilisable. Celui-ci supporte les modules chargés paresseux comme @ Uğur Dinç et intègre l'indicateur de données de route @Davor. La meilleure amélioration est la génération automatique d’un identifiant (presque) unique basé sur le chemin absolu de la page. De cette façon, vous n'avez pas à le définir vous-même sur chaque page.

Marquez toute page que vous souhaitez mettre en cache en définissant reuseRoute: true. Il sera utilisé dans la méthode shouldDetach.

{
  path: '',
  component: MyPageComponent,
  data: { reuseRoute: true },
}

Celui-ci est l'implémentation de stratégie la plus simple, sans comparer les paramètres de requête.

import { ActivatedRouteSnapshot, RouteReuseStrategy, DetachedRouteHandle, UrlSegment } from '@angular/router'

export class CustomReuseStrategy implements RouteReuseStrategy {

  storedHandles: { [key: string]: DetachedRouteHandle } = {};

  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    return route.data.reuseRoute || false;
  }

  store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
    const id = this.createIdentifier(route);
    if (route.data.reuseRoute) {
      this.storedHandles[id] = handle;
    }
  }

  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    const id = this.createIdentifier(route);
    const handle = this.storedHandles[id];
    const canAttach = !!route.routeConfig && !!handle;
    return canAttach;
  }

  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
    const id = this.createIdentifier(route);
    if (!route.routeConfig || !this.storedHandles[id]) return null;
    return this.storedHandles[id];
  }

  shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    return future.routeConfig === curr.routeConfig;
  }

  private createIdentifier(route: ActivatedRouteSnapshot) {
    // Build the complete path from the root to the input route
    const segments: UrlSegment[][] = route.pathFromRoot.map(r => r.url);
    const subpaths = ([] as UrlSegment[]).concat(...segments).map(segment => segment.path);
    // Result: ${route_depth}-${path}
    return segments.length + '-' + subpaths.join('/');
  }
}

Celui-ci compare également les paramètres de requête. compareObjects présente une légère amélioration par rapport à la version de @Corbfon: boucle à travers les propriétés des objets de base et de comparaison. N'oubliez pas que vous pouvez utiliser une implémentation externe plus fiable telle que la méthode lodash isEqual.

import { ActivatedRouteSnapshot, RouteReuseStrategy, DetachedRouteHandle, UrlSegment } from '@angular/router'

interface RouteStorageObject {
  snapshot: ActivatedRouteSnapshot;
  handle: DetachedRouteHandle;
}

export class CustomReuseStrategy implements RouteReuseStrategy {

  storedRoutes: { [key: string]: RouteStorageObject } = {};

  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    return route.data.reuseRoute || false;
  }

  store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
    const id = this.createIdentifier(route);
    if (route.data.reuseRoute && id.length > 0) {
      this.storedRoutes[id] = { handle, snapshot: route };
    }
  }

  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    const id = this.createIdentifier(route);
    const storedObject = this.storedRoutes[id];
    const canAttach = !!route.routeConfig && !!storedObject;
    if (!canAttach) return false;

    const paramsMatch = this.compareObjects(route.params, storedObject.snapshot.params);
    const queryParamsMatch = this.compareObjects(route.queryParams, storedObject.snapshot.queryParams);

    console.log('deciding to attach...', route, 'does it match?');
    console.log('param comparison:', paramsMatch);
    console.log('query param comparison', queryParamsMatch);
    console.log(storedObject.snapshot, 'return: ', paramsMatch && queryParamsMatch);

    return paramsMatch && queryParamsMatch;
  }

  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
    const id = this.createIdentifier(route);
    if (!route.routeConfig || !this.storedRoutes[id]) return null;
    return this.storedRoutes[id].handle;
  }

  shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    return future.routeConfig === curr.routeConfig;
  }

  private createIdentifier(route: ActivatedRouteSnapshot) {
    // Build the complete path from the root to the input route
    const segments: UrlSegment[][] = route.pathFromRoot.map(r => r.url);
    const subpaths = ([] as UrlSegment[]).concat(...segments).map(segment => segment.path);
    // Result: ${route_depth}-${path}
    return segments.length + '-' + subpaths.join('/');
  }

  private compareObjects(base: any, compare: any): boolean {

    // loop through all properties
    for (const baseProperty in { ...base, ...compare }) {

      // determine if comparrison object has that property, if not: return false
      if (compare.hasOwnProperty(baseProperty)) {
        switch (typeof base[baseProperty]) {
          // if one is object and other is not: return false
          // if they are both objects, recursively call this comparison function
          case 'object':
            if (typeof compare[baseProperty] !== 'object' || !this.compareObjects(base[baseProperty], compare[baseProperty])) {
              return false;
            }
            break;
          // if one is function and other is not: return false
          // if both are functions, compare function.toString() results
          case 'function':
            if (typeof compare[baseProperty] !== 'function' || base[baseProperty].toString() !== compare[baseProperty].toString()) {
              return false;
            }
            break;
          // otherwise, see if they are equal using coercive comparison
          default:
            // tslint:disable-next-line triple-equals
            if (base[baseProperty] != compare[baseProperty]) {
              return false;
            }
        }
      } else {
        return false;
      }
    }

    // returns true only after false HAS NOT BEEN returned through all loops
    return true;
  }
}

Si vous avez un meilleur moyen de générer des clés uniques commenter ma réponse, je mettrai à jour le code.

Merci à tous les gars qui ont partagé leur solution.

1
McGiogen