Hey Anders, tolle Frage!
Ich habe fast den gleichen Anwendungsfall wie Sie und wollte das Gleiche tun! Benutzersuche> Ergebnisse abrufen> Benutzer navigiert zum Ergebnis> Benutzer navigiert zurück> BOOM blitzschnelle Rückkehr zu den Ergebnissen , aber Sie möchten nicht das spezifische Ergebnis speichern, zu dem der Benutzer navigiert hat.
tl; dr
Sie benötigen eine Klasse, RouteReuseStrategy
die Ihre Strategie in der implementiert und bereitstellt ngModule
. Wenn Sie ändern möchten, wann die Route gespeichert ist, ändern Sie die shouldDetach
Funktion. Bei der Rückkehr true
speichert Angular die Route. Wenn Sie ändern möchten, wenn die Route angehängt ist, ändern Sie die shouldAttach
Funktion. WannshouldAttach
true zurückgegeben wird, verwendet Angular die gespeicherte Route anstelle der angeforderten Route. Hier ist ein Plunker mit dem Sie .
Informationen zu RouteReuseStrategy
Wenn Sie diese Frage gestellt haben, wissen Sie bereits, dass Sie mit RouteReuseStrategy Angular anweisen können , eine Komponente nicht zu zerstören, sondern sie für ein späteres erneutes Rendern zu speichern. Das ist cool, weil es erlaubt:
- Verringert Serveraufrufe
- Ist gestiegen Geschwindigkeit
- UND die Komponente rendert standardmäßig in dem Zustand, in dem sie belassen wurde
Letzteres ist wichtig, wenn Sie beispielsweise eine Seite vorübergehend verlassen möchten, obwohl der Benutzer viel Text eingegeben hat . Unternehmensanwendungen werden diese Funktion wegen des Übermaßes lieben Anzahl von Formularen !
Dies ist, was ich mir ausgedacht habe, um das Problem zu lösen. Wie Sie sagten, müssen Sie die nutzenRouteReuseStrategy
von @ angle / router in Versionen 3.4.1 und höher angebotene verwenden.
MACHEN
Zuerst Sie zunächst sicher, dass Ihr Projekt über @ angle / router Version 3.4.1 oder höher verfügt.
Erstellen Sie als Nächstes eine Datei, in der Ihre implementierte Klasse gespeichert wird RouteReuseStrategy
. Ich rief meine an reuse-strategy.ts
und legte sie zur /app
sicheren Aufbewahrung in den Ordner. Im Moment sollte diese Klasse folgendermaßen aussehen:
import { RouteReuseStrategy } from '@angular/router';
export class CustomReuseStrategy implements RouteReuseStrategy {
}
(Machen Sie sich keine Sorgen um Ihre TypeScript-Fehler, wir werden alles lösen.)
Beenden Sie die Grundlagen, indem Sie die Klasse für Sie bereitstellen app.module
. Beachten Sie, dass Sie noch nicht geschrieben haben CustomReuseStrategy
, sollte aber voran gehen und import
es von reuse-strategy.ts
allen gleich. Ebenfallsimport { RouteReuseStrategy } from '@angular/router';
@NgModule({
[...],
providers: [
{provide: RouteReuseStrategy, useClass: CustomReuseStrategy}
]
)}
export class AppModule {
}
Das letzte Stück schreibt die Klasse, die steuert, ob Routen getrennt, gespeichert, abgerufen und wieder angehängt werden. Bevor wir zum alten Kopieren / Einfügen kommen , werde ich hier eine kurze Erklärung der Mechanik geben, so wie ich sie verstehe. Verweisen Sie auf den folgenden Code für die Methoden, die ich beschreibe, und natürlich enthält der Code zahlreiche Dokumentationen .
- Wenn Sie navigieren, wird
shouldReuseRoute
ausgelöst. Dieser ist ein bisschen seltsam für mich, aber wenn er zurückkommttrue
, wird die Route, auf der Sie sich gerade befinden, tatsächlich wiederverwendet, und keine der anderen Methoden wird ausgelöst. Ich gebe nur false zurück, wenn der Benutzer weg navigiert.
- Wenn
shouldReuseRoute
zurückkommt false
, wird shouldDetach
ausgelöst. shouldDetach
Legt fest, ob Sie die Route speichern möchten oder nicht, und gibt eine entsprechende boolean
Angabe zurück. Hier sollten Sie entscheiden, Pfade zu speichern / nicht zu speichern. Dies würde ich tun, indem ich ein Array von Pfaden überprüfe, für die Sie gespeichert werden möchtenroute.routeConfig.path
, und false zurückgeben würde, wenn das path
nicht im Array vorhanden ist.
- Wenn
shouldDetach
zurückgegeben wird true
, store
wird dies ausgelöst. Dies ist eine Gelegenheit für Sie, alle gewünschten Informationen über die Route zu speichern. Was auch immer Sie tun, Sie müssen das speichern, DetachedRouteHandle
da Angular dies verwendet, um Ihre gespeicherte Komponente später zu identifizieren. Unten speichere ich sowohl das DetachedRouteHandle
als auch das ActivatedRouteSnapshot
in einer Variablen, die für meine Klasse lokal ist.
Wir haben also die Logik für die Speicherung gesehen, aber wie sieht es mit der Navigation zu einer Komponente aus? Wie beschließt Angular, Ihre Navigation abzufangen und die gespeicherte an ihre Stelle zu setzen?
- Nach
shouldReuseRoute
der Rückkehr false
wird erneut ausgeführt. shouldAttach
Dies ist Ihre Chance, herauszufinden, ob Sie die Komponente neu generieren oder im Speicher verwenden möchten. Wenn Sie eine gespeicherte Komponente wiederverwenden möchten, geben Sie sie zurücktrue
Sie und Sie sind auf einem guten Weg!
- Nun Angular Sie werden fragen, „welche Komponente möchten Sie uns benutzen?“, Die Sie durch Rücksendung dieser Komponente anzeigt
DetachedRouteHandle
aus retrieve
.
Das ist so ziemlich die ganze Logik, die Sie brauchen! Im folgenden Code für reuse-strategy.ts
habe ich Ihnen auch eine raffinierte Funktion hinterlassen, mit der zwei Objekte verglichen werden. Ich benutze es, um die zukünftigen Routen route.params
und route.queryParams
die gespeicherten zu vergleichen. Wenn alle übereinstimmen, möchte ich die gespeicherte Komponente verwenden, anstatt eine neue zu generieren. Aber wie du es machst, liegt bei dir!
Wiederverwendungsstrategie.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;
}
}
Verhalten
Diese Implementierung speichert jede eindeutige Route, die der Benutzer auf dem Router besucht, genau einmal. Dadurch werden die im Speicher gespeicherten Komponenten während der gesamten Benutzersitzung auf der Site weiter erweitert. Wenn Sie die von Ihnen gespeicherten Routen einschränken möchten, ist dies die shouldDetach
Methode. Es steuert, welche Routen Sie speichern.
Beispiel
Angenommen, Ihr Benutzer sucht auf der Startseite nach etwas, das ihn zu dem Pfad navigiert search/:term
, der möglicherweise so aussieht www.yourwebsite.com/search/thingsearchedfor
. Die Suchseite enthält eine Reihe von Suchergebnissen. Sie möchten diese Route speichern, falls sie darauf zurückkommen möchten! Jetzt klicken sie auf ein Suchergebnis und navigieren zu dem view/:resultId
, das Sie nicht speichern möchten, da sie wahrscheinlich nur einmal dort sein werden. Mit der obigen Implementierung würde ich einfach die shouldDetach
Methode ändern ! So könnte es aussehen:
Zunächst einmal wollen wir ein Array von Pfaden machen wir speichern möchten.
private acceptedRoutes: string[] = ["search/:term"];
Jetzt können shouldDetach
wir das route.routeConfig.path
mit unserem Array vergleichen.
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
}
}
Da Angular nur eine Instanz einer Route speichert , ist dieser Speicher leicht und wir speichern nur die Komponente, die sich unter befindet, search/:term
und nicht alle anderen!
Zusätzliche Links
Obwohl es noch nicht viel Dokumentation gibt, gibt es hier ein paar Links zu dem, was existiert:
Angular Docs: https://angular.io/docs/ts/latest/api/router/index/RouteReuseStrategy-class.html
Einführungsartikel: https://www.softwarearchitekt.at/post/2016/12/02/sticky-routes-in-angular-2-3-with-routereusestrategy.aspx
Standardimplementierung von RouteReuseStrategy durch nativescript- angle : https://github.com/NativeScript/nativescript-angular/blob/cb4fd3a/nativescript-angular/router/ns-route-reuse-strategy.ts