143 votes

Comment mettre en œuvre la stratégie RouteReuseStrategy shouldDetach pour des routes spécifiques en Angular 2 ?

J'ai un module Angular 2 dans lequel j'ai implémenté le routage et je voudrais que les états soient stockés lors de la navigation.
L'utilisateur doit être en mesure de :

  1. rechercher des documents à l'aide d'une "formule de recherche".
  2. naviguer vers l'un des résultats
  3. retourner à 'searchresult' - sans communiquer avec le serveur

Cela est possible notamment grâce à RouteReuseStrategy .
La question est la suivante :
Comment mettre en œuvre le fait que le document ne doit pas être stocké ?

Donc l'état du chemin d'accès "documents" doit être stocké et l'état du chemin d'accès "documents/:id" ne doit PAS être stocké ?

251voto

Corbfon Points 1766

Hey Anders, excellente question !

J'ai presque le même cas d'utilisation que vous, et je voulais faire la même chose ! L'utilisateur effectue une recherche > obtient des résultats > L'utilisateur navigue vers le résultat > L'utilisateur navigue à nouveau > BOOM retour rapide aux résultats mais vous ne voulez pas stocker le résultat spécifique vers lequel l'utilisateur a navigué.

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ù l'itinéraire est stocké, modifiez le champ shouldDetach fonction. Lorsqu'elle renvoie true Angular stocke la route. Si vous voulez modifier le moment où la route est attachée, modifiez le paramètre shouldAttach fonction. Lorsque shouldAttach retourne vrai, Angular utilisera l'itinéraire stocké à la place de l'itinéraire demandé. Voici une Plunker pour que vous puissiez jouer avec.

À propos de RouteReuseStrategy

En ayant posé cette question, vous comprenez déjà que RouteReuseStrategy vous permet d'indiquer à Angular no pour détruire un composant, mais en fait pour le sauvegarder en vue d'un nouveau rendu à une date ultérieure. C'est cool parce que ça permet :

  • Diminution de appels du serveur
  • Augmentation de vitesse
  • ET le composant est rendu, par défaut, dans l'état dans lequel il a été laissé.

Ce dernier point est important si vous souhaitez, par exemple, quitter temporairement une page même si l'utilisateur a saisi un mot de passe. lot de texte dans celui-ci. Les applications d'entreprise vont adorer cette fonctionnalité en raison de la excessif quantité de formulaires !

Voici la solution que j'ai trouvée pour résoudre le problème. Comme vous l'avez dit, vous devez utiliser la fonction RouteReuseStrategy offert par @angular/router dans les versions 3.4.1 et supérieures.

TODO

Premier Assurez-vous que votre projet possède @angular/router version 3.4.1 ou supérieure.

Suivant créez un fichier qui accueillera votre classe qui implémente RouteReuseStrategy . J'ai appelé le mien reuse-strategy.ts et l'a placé dans le /app pour les conserver. Pour l'instant, cette classe devrait ressembler à :

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

export class CustomReuseStrategy implements RouteReuseStrategy {
}

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

Finir le travail de fond en fournissant la classe à votre app.module . Notez que vous n'avez pas encore écrit CustomReuseStrategy mais je devrais aller de l'avant et import de reuse-strategy.ts tout de même. Aussi import { RouteReuseStrategy } from '@angular/router';

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

La pièce finale écrit la classe qui va contrôler si oui ou non les routes sont détachées, stockées, récupérées et rattachées. Avant de passer à l'ancien copier/coller Je vais expliquer brièvement les mécanismes ici, tels que je les comprends. Référez-vous au 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. Quand vous naviguez, shouldReuseRoute feux. Celui-ci est un peu étrange pour moi, mais s'il revient true alors il réutilise l'itinéraire sur lequel vous êtes actuellement et aucune des autres méthodes n'est déclenchée. Je renvoie simplement false si l'utilisateur s'éloigne.
  2. Si shouldReuseRoute renvoie à false , shouldDetach feux. shouldDetach détermine si vous souhaitez ou non enregistrer l'itinéraire, et renvoie un message de type boolean qui l'indique. C'est ici que vous devez décider de stocker ou non les chemins. ce que je ferais en vérifiant un tableau de chemins d'accès. veulent stocké contre route.routeConfig.path et retourne false si le path n'existe pas dans le tableau.
  3. Si shouldDetach renvoie à true , store est déclenché, ce qui vous donne l'occasion de stocker toutes les informations que vous souhaitez sur l'itinéraire. Quoi que vous fassiez, vous devrez stocker le fichier DetachedRouteHandle car c'est ce qu'Angular utilise pour identifier votre composant stocké par la suite. Ci-dessous, je stocke à la fois le DetachedRouteHandle et le ActivatedRouteSnapshot dans une variable locale de ma classe.

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

  1. Encore une fois, après shouldReuseRoute est revenu false , shouldAttach s'exécute, ce qui vous permet de déterminer si vous voulez régénérer ou utiliser le composant en mémoire. Si vous voulez réutiliser un composant stocké, retournez la commande 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 l'adresse du composant en question. DetachedRouteHandle de retrieve .

C'est à peu près toute la logique dont vous avez besoin ! Dans le code de reuse-strategy.ts Je vous ai également laissé une fonction astucieuse qui permet de comparer deux objets. Je l'utilise pour comparer l'itinéraire du futur route.params y route.queryParams avec ceux qui sont stockés. Si elles correspondent toutes, je veux utiliser le composant stocké au lieu d'en générer un nouveau. Mais comment le faire à vous de choisir !

stratégie de réutilisation.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 route 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 stockez, l'endroit où le faire est l'onglet shouldDetach méthode. Elle contrôle les itinéraires que vous sauvegardez.

Exemple

Disons que votre utilisateur recherche quelque chose à partir de la page d'accueil, ce qui l'amène au chemin search/:term ce qui pourrait ressembler à www.yourwebsite.com/search/thingsearchedfor . La page de recherche contient un ensemble de résultats de recherche. Vous aimeriez stocker cet itinéraire, au cas où ils voudraient y revenir ! Maintenant, ils cliquent sur un résultat de recherche et sont dirigés vers view/:resultId que vous ne pas veulent stocker, vu qu'ils ne seront probablement là qu'une seule fois. Avec l'implémentation ci-dessus en place, je modifierais simplement le fichier shouldDetach méthode ! Voici à quoi cela pourrait ressembler :

Tout d'abord faisons un tableau des chemins que nous voulons stocker.

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

maintenant, dans shouldDetach nous pouvons vérifier le route.routeConfig.path contre 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 qu'Angular ne stocke qu'une seule instance d'un itinéraire, ce stockage sera léger, et nous ne stockerons que le composant situé à l'adresse 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 :

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

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

L'implémentation par défaut de nativescript-angular de Stratégie de réutilisation des routes : https://github.com/NativeScript/nativescript-angular/blob/cb4fd3a/nativescript-angular/router/ns-route-reuse-strategy.ts

0 votes

Merci Corbfon pour cette explication approfondie ! Cela m'a aidé à mieux comprendre la stratégie RouteReuseStrategy. Mon problème était de ne PAS stocker des routes spécifiques. J'ai découvert que la fonction shouldDetach était centrale et vérifiait simplement la longueur de l'itinéraire actuel : shouldDetach(route: ActivatedRouteSnapshot): boolean { if (route.url.length > 1) { return false; //not stored } else { return true; //stored } } . Cela ne suffirait-il pas à résoudre mon problème ?

0 votes

Hey Anders, shouldDetach est en effet l'endroit où vous devez prendre la décision de stocker votre itinéraire. L'implémentation que vous avez ci-dessus ne stockerait que le chemin Root, puisque c'est le chemin pour lequel la fonction router.url.length array = 0. Je choisirais un chemin que vous souhaitez stocker, ou un tableau de chemins que vous souhaitez stocker, et je vérifierais le chemin par rapport à ce tableau à l'aide de la commande forEach et le route.routeConfig.path propriété. Je reviendrai et modifierai ma réponse pour mieux répondre à votre question.

0 votes

@Corbfon avez-vous essayé cela sur des chemins d'enfants ? J'ai posté une question concernant une erreur que je reçois : stackoverflow.com/questions/41584664/

50voto

Chris Fremgen Points 295

Ne vous laissez pas intimider par la réponse acceptée, c'est assez simple. Voici une réponse rapide à ce dont vous avez besoin. Je vous recommande de lire au moins la réponse acceptée, car elle est pleine de détails.

Cette solution ne fait pas de 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;
 }
}

1 votes

Cela fonctionnera-t-il également pour les routes qui sont chargées paresseusement ?

2 votes

RouteConfig est null, pour différentes routes, donc shouldReuseRoute retournera toujours true ce qui n'est pas le comportement désiré

30voto

Davor Points 130

En plus de la réponse acceptée (par Corbfon) et de l'explication plus courte et plus directe de Chris Fremgen, je veux ajouter une façon plus flexible de traiter les routes 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 et vérifient ensuite si le chemin actuel de la route est dans le tableau ou non. Cette vérification est effectuée dans shouldDetach méthode.

Je trouve cette approche peu flexible car si nous voulons changer le nom de l'itinéraire, nous devons nous souvenir de changer également le nom de l'itinéraire dans notre fichier CustomReuseStrategy classe. 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 connaître l'existence de la classe RouteReuseStrategy .

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

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

Et ensuite dans shouldDetach méthode que nous utilisons.

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

1 votes

Bonne solution. Cela devrait vraiment être intégré dans la stratégie standard de réutilisation des routes angulaires avec un simple drapeau comme celui que vous avez appliqué.

0 votes

Excellente réponse. Merci beaucoup !

21voto

McGiogen Points 325

Une autre mise en œuvre plus valide, complète et réutilisable. Celle-ci supporte les modules chargés paresseusement comme @Ugur Dinç et intègre @Davor route data flag. 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 les pages que vous souhaitez mettre en cache reuseRoute: true . Il sera utilisé dans shouldDetach méthode.

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

Celle-ci est la mise en œuvre de la stratégie la plus simple, sans comparaison des paramètres de la 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 la requête. compareObjects a une petite amélioration par rapport à la version de @Corbfon : boucle à travers les propriétés des objets de base et de comparaison. Rappelez-vous que vous pouvez utiliser une implémentation externe et plus fiable comme lodash isEqual méthode.

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 une meilleure façon de générer des clés uniques, commentez ma réponse, je mettrai à jour le code.

Merci à tous ceux qui ont partagé leur solution.

4 votes

Cela devrait être la réponse acceptée. De nombreuses solutions proposées ci-dessus ne peuvent pas prendre en charge plusieurs pages avec la même URL enfant. Parce qu'elles comparent l'URL activatedRoute, qui n'est pas le chemin complet.

0 votes

Excellente solution ! Juste pour mentionner que si vous voulez supprimer les composants stockés lorsque l'utilisateur se déconnecte, vous pourriez faire quelque chose comme ceci dans le fichier shouldAttach crochet if (route.component === AuthComponent ){ this.storedHandles = {}; return false; }

15voto

dexter Points 1206

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'informations aquí .

1 votes

Merci de l'avoir ajouté ! Je dois l'essayer. Cela pourrait même résoudre certains des problèmes de gestion des routes enfant que ma solution rencontre.

0 votes

J'ai dû utiliser route.data["key"] à construire sans erreur. Mais le problème que je rencontre est que j'ai une route+composante qui est utilisée à deux endroits différents. 1. sample/list/item y 2. product/id/sample/list/item Lorsque je charge l'un ou l'autre des chemins pour la première fois, il se charge bien mais l'autre envoie l'erreur de rattachement parce que je stocke sur la base de list/item J'ai donc dupliqué l'itinéraire et modifié le chemin d'accès à l'URL, mais le même composant s'affiche. Je ne suis pas sûr qu'il existe un autre moyen de contourner ce problème.

0 votes

Cela m'a troublé, la méthode ci-dessus ne fonctionnait pas, elle s'effondrait dès que j'atteignais l'une de mes routes en cache (elle ne naviguait plus et il y avait des erreurs dans la console). La solution de Chris Fremgen semble fonctionner correctement avec mes modules paresseux, pour autant que je puisse en juger jusqu'à présent...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X