Salut Anders, excellente question!
J'ai presque le même cas d'utilisation que vous et je voulais faire la même chose! Recherche utilisateur> obtenir les résultats> L'utilisateur accède au résultat> L'utilisateur revient en arrière> BOOM retourne rapidement 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 fournit votre stratégie dans le ngModule
. Si vous souhaitez modifier le moment où l'itinéraire est enregistré, modifiez la shouldDetach
fonction. Lorsqu'il revient true
, Angular stocke l'itinéraire. Si vous souhaitez modifier le moment où l'itinéraire est attaché, modifiez la shouldAttach
fonction. Lorsque le shouldAttach
retour est vrai, Angular utilisera l'itinéraire stocké à la place de l'itinéraire demandé. Voici un Plunker avec lequel vous pouvez jouer.
À propos de RouteReuseStrategy
En ayant posé cette question, vous comprenez déjà que RouteReuseStrategy vous permet de dire à Angular de ne pas détruire un composant, mais en fait de le sauvegarder pour un re-rendu à une date ultérieure. C'est cool car cela permet:
- Diminution des appels au serveur
- Augmentation de la vitesse
- ET le composant restitue, par défaut, dans le même état dans lequel il a été laissé
Ce dernier est important si vous souhaitez, par exemple, quitter une page temporairement même si l'utilisateur y a entré beaucoup de texte. Les applications d'entreprise adoreront cette fonctionnalité en raison du nombre excessif de formulaires!
C'est ce que j'ai proposé pour résoudre le problème. Comme vous l'avez dit, vous devez utiliser l' RouteReuseStrategy
offre de @ angular / router dans les versions 3.4.1 et supérieures.
FAIRE
Assurez-vous d' abord que votre projet a @ angular / router version 3.4.1 ou supérieure.
Ensuite , créez un fichier qui hébergera votre classe qui implémente RouteReuseStrategy
. J'ai appelé le mien reuse-strategy.ts
et l' ai placé dans le /app
dossier pour le garder en lieu sûr. 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)
Terminez le travail de base en fournissant la classe à votre app.module
. Notez que vous n'avez pas encore écrit CustomReuseStrategy
, mais que vous devriez continuer et import
cela de reuse-strategy.ts
tout de même. Aussiimport { RouteReuseStrategy } from '@angular/router';
@NgModule({
[...],
providers: [
{provide: RouteReuseStrategy, useClass: CustomReuseStrategy}
]
)}
export class AppModule {
}
Le dernier élément est l'écriture de la classe qui contrôlera si les routes sont ou non détachées, stockées, récupérées et rattachées. Avant d'arriver à l'ancien copier / coller , je vais faire une brève explication des mécanismes ici, tels que je les 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 .
- Lorsque vous naviguez, se
shouldReuseRoute
déclenche. Celui-ci est un peu étrange pour moi, mais s'il revient true
, il réutilise en fait 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.
- Si
shouldReuseRoute
revient false
, se shouldDetach
déclenche. shouldDetach
détermine si vous souhaitez ou non stocker l'itinéraire et renvoie un boolean
indiquant autant. C'est là que vous devez décider de stocker / ne pas stocker les chemins , ce que je ferais en vérifiant un tableau de chemins que vous souhaitez stocker route.routeConfig.path
, et en retournant false si le path
n'existe pas dans le tableau.
- Si
shouldDetach
retourne true
, store
est déclenché, ce qui est une opportunité pour vous de stocker toutes les informations que vous souhaitez sur l'itinéraire. Quoi que vous fassiez, vous devrez stocker le DetachedRouteHandle
car c'est ce que Angular utilise pour identifier votre composant stocké plus tard. Ci-dessous, je stocke le DetachedRouteHandle
et le ActivatedRouteSnapshot
dans une variable locale de ma classe.
Nous avons donc vu la logique du stockage, mais qu'en est-il de la navigation vers un composant? Comment Angular décide-t-il d'intercepter votre navigation et de remettre celle stockée à sa place?
- Encore une fois, après le
shouldReuseRoute
retour false
, shouldAttach
s'exécute, ce qui est votre chance 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é, revenez true
et vous êtes sur la bonne voie!
- Maintenant angulaire vous demander: « quel composant vous nous souhaitez utiliser? », Que vous indiquerez en retournant c'est composant à
DetachedRouteHandle
partir retrieve
.
C'est à peu près toute la logique dont vous avez besoin! Dans le code reuse-strategy.ts
ci-dessous, je vous ai également laissé une fonction astucieuse qui comparera deux objets. Je l'utilise pour comparer les futurs itinéraires route.params
et ceux route.queryParams
enregistrés. Si tout cela correspond, je souhaite utiliser le composant stocké au lieu d'en générer un nouveau. Mais comment vous le faites dépend de 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 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 la shouldDetach
méthode. Il contrôle les itinéraires que vous enregistrez.
Exemple
Supposons que votre utilisateur recherche quelque chose sur la page d'accueil, ce qui le dirige vers le chemin search/:term
, qui peut ressembler à www.yourwebsite.com/search/thingsearchedfor
. La page de recherche contient un tas de résultats de recherche. Vous souhaitez enregistrer cet itinéraire, au cas où ils voudraient y revenir! Maintenant, ils cliquent sur un résultat de recherche et accèdent à view/:resultId
ce que vous ne voulez pas stocker, car ils ne seront probablement là qu'une seule fois. Avec l'implémentation ci-dessus en place, je changerais simplement la shouldDetach
méthode! Voici à quoi cela pourrait ressembler:
Tout d'abord , créons un tableau de chemins que nous voulons stocker.
private acceptedRoutes: string[] = ["search/:term"];
maintenant, 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
}
}
Comme Angular ne stockera qu'une seule instance d'une route, ce stockage sera léger, et nous ne stockerons que le composant situé sur 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:
Docs angulaires: https://angular.io/docs/ts/latest/api/router/index/RouteReuseStrategy-class.html
Article d'introduction: https://www.softwarearchitekt.at/post/2016/12/02/sticky-routes-in-angular-2-3-with-routereusestrategy.aspx
Implémentation par défaut de RouteReuseStrategy par nativescript-angular : https://github.com/NativeScript/nativescript-angular/blob/cb4fd3a/nativescript-angular/router/ns-route-reuse-strategy.ts