Ehi Anders, ottima domanda!
Ho quasi lo stesso caso d'uso di te e volevo fare la stessa cosa! Ricerca utente> ottieni risultati> L'utente naviga fino al risultato> L'utente torna indietro> BOOM velocissimo ritorno ai risultati , ma non si desidera memorizzare il risultato specifico a cui l'utente è andato.
tl; dr
È necessario disporre di una classe che implementa RouteReuseStrategy
e fornisce la propria strategia in ngModule
. Se vuoi modificare quando il percorso è memorizzato, modifica la shouldDetach
funzione. Quando ritorna true
, Angular memorizza il percorso. Se vuoi modificare quando la rotta è collegata, modifica la shouldAttach
funzione. Quando shouldAttach
restituisce true, Angular utilizzerà la rotta memorizzata al posto della rotta richiesta. Ecco un Plunker con cui giocare.
Informazioni su RouteReuseStrategy
Avendo posto questa domanda, hai già capito che RouteReuseStrategy ti consente di dire ad Angular di non distruggere un componente, ma di salvarlo per il rendering in un secondo momento. È fantastico perché consente:
- Diminuzione delle chiamate al server
- Velocità aumentata
- E il componente viene visualizzato, per impostazione predefinita, nello stesso stato in cui è stato lasciato
Quest'ultimo è importante se, ad esempio, desideri lasciare una pagina temporaneamente anche se l'utente ha inserito molto testo in essa. Le applicazioni aziendali apprezzeranno questa funzione a causa dell'eccessiva quantità di moduli!
Questo è ciò che ho pensato per risolvere il problema. Come hai detto, devi utilizzare l' RouteReuseStrategy
offerta offerta da @ angular / router nelle versioni 3.4.1 e successive.
FARE
Innanzitutto assicurati che il tuo progetto abbia @ angular / router versione 3.4.1 o successiva.
Quindi , crea un file che ospiterà la tua classe che implementa RouteReuseStrategy
. Ho chiamato il mio reuse-strategy.ts
e l' ho messo nella /app
cartella per custodia. Per ora, questa classe dovrebbe assomigliare a:
import { RouteReuseStrategy } from '@angular/router';
export class CustomReuseStrategy implements RouteReuseStrategy {
}
(non preoccuparti per i tuoi errori TypeScript, stiamo per risolvere tutto)
Termina le basi fornendo la lezione al tuo app.module
. Si noti che non è ancora stato scritto CustomReuseStrategy
, ma deve andare avanti e import
da reuse-strategy.ts
tutto lo stesso. Ancheimport { RouteReuseStrategy } from '@angular/router';
@NgModule({
[...],
providers: [
{provide: RouteReuseStrategy, useClass: CustomReuseStrategy}
]
)}
export class AppModule {
}
Il pezzo finale è scrivere la classe che controllerà se le rotte vengono scollegate, archiviate, recuperate e ricollegate. Prima di arrivare al vecchio copia / incolla , farò qui una breve spiegazione delle meccaniche, come ho capito. Fai riferimento al codice seguente per i metodi che sto descrivendo e, naturalmente, c'è molta documentazione nel codice .
- Quando navighi,
shouldReuseRoute
spara. Questo è un po 'strano per me, ma se ritorna true
, in realtà riutilizza il percorso su cui ti trovi attualmente e nessuno degli altri metodi viene attivato. Restituisco solo false se l'utente sta navigando.
- Se
shouldReuseRoute
ritorna false
, shouldDetach
spara. shouldDetach
determina se si desidera o meno memorizzare il percorso e restituisce un che ne boolean
indica tanto. Qui è dove dovresti decidere di memorizzare / non memorizzare i percorsi , cosa che farei controllando un array di percorsi su cui desideri archiviare route.routeConfig.path
e restituendo false se path
non esiste nell'array.
- Se
shouldDetach
ritorna true
, store
viene attivato, che è un'opportunità per te di memorizzare tutte le informazioni che desideri sul percorso. Qualunque cosa tu faccia, dovrai memorizzare il DetachedRouteHandle
perché è ciò che Angular usa per identificare il tuo componente memorizzato in seguito. Di seguito, memorizzo sia il DetachedRouteHandle
che il ActivatedRouteSnapshot
in una variabile locale della mia classe.
Quindi, abbiamo visto la logica per l'archiviazione, ma per quanto riguarda la navigazione verso un componente? In che modo Angular decide di intercettare la tua navigazione e di rimettere quella memorizzata al suo posto?
- Di nuovo, dopo che
shouldReuseRoute
è tornato false
, shouldAttach
viene eseguito, che è la tua occasione per capire se vuoi rigenerare o usare il componente in memoria. Se vuoi riutilizzare un componente immagazzinato, torna true
e sei sulla buona strada!
- Ora Angular ti chiederà "quale componente vuoi che usiamo?", Che indicherai restituendo quel componente
DetachedRouteHandle
da retrieve
.
Questa è praticamente tutta la logica di cui hai bisogno! Nel codice per reuse-strategy.ts
, di seguito, ti ho anche lasciato un'elegante funzione che confronterà due oggetti. Io lo uso per confrontare i del percorso futuro route.params
e route.queryParams
con le stored propri. Se tutti corrispondono, voglio utilizzare il componente memorizzato invece di generarne uno nuovo. Ma come lo fai dipende da te!
riutilizzo-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;
}
}
Comportamento
Questa implementazione memorizza ogni percorso univoco che l'utente visita sul router esattamente una volta. Ciò continuerà ad aggiungersi ai componenti archiviati in memoria durante la sessione dell'utente sul sito. Se desideri limitare i percorsi che memorizzi, il posto giusto per farlo è il shouldDetach
metodo. Controlla quali rotte salvi.
Esempio
Supponiamo che il tuo utente cerchi qualcosa dalla home page, che lo indirizza al percorso search/:term
, che potrebbe apparire come www.yourwebsite.com/search/thingsearchedfor
. La pagina di ricerca contiene una serie di risultati di ricerca. Vorresti memorizzare questo percorso, nel caso volessero tornare su di esso! Ora fanno clic su un risultato di ricerca e vengono indirizzati a view/:resultId
, che non si desidera memorizzare, visto che probabilmente saranno presenti solo una volta. Con l'implementazione di cui sopra, cambierei semplicemente il shouldDetach
metodo! Ecco come potrebbe apparire:
Prima di tutto creiamo una serie di percorsi che vogliamo memorizzare.
private acceptedRoutes: string[] = ["search/:term"];
ora, shouldDetach
possiamo controllare il route.routeConfig.path
contro il nostro array.
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
}
}
Poiché Angular memorizzerà solo un'istanza di un percorso, questa memoria sarà leggera e memorizzeremo solo il componente situato in search/:term
e non tutti gli altri!
Collegamenti aggiuntivi
Sebbene non ci sia ancora molta documentazione là fuori, ecco un paio di collegamenti a ciò che esiste:
Documenti angolari: https://angular.io/docs/ts/latest/api/router/index/RouteReuseStrategy-class.html
Articolo introduttivo: https://www.softwarearchitekt.at/post/2016/12/02/sticky-routes-in-angular-2-3-with-routereusestrategy.aspx
Implementazione predefinita di nativescript - angular di RouteReuseStrategy : https://github.com/NativeScript/nativescript-angular/blob/cb4fd3a/nativescript-angular/router/ns-route-reuse-strategy.ts