So implementieren Sie RouteReuseStrategy shouldDetach für bestimmte Routen in Angular 2

113

Ich habe ein Angular 2-Modul, in dem ich Routing implementiert habe, und möchte, dass die Zustände beim Navigieren gespeichert werden. Der Benutzer sollte in der Lage sein: 1. Dokumente mithilfe einer Suchformel zu suchen 2. zu einem der Ergebnisse zu navigieren 3. zum Suchergebnis zurückzukehren - ohne mit dem Server zu kommunizieren

Dies ist einschließlich RouteReuseStrategy möglich. Die Frage ist: Wie implementiere ich, dass das Dokument nicht gespeichert werden soll?

Der Status des Routenpfads "Dokumente" sollte also gespeichert werden, und der Status des Routenpfads "Dokumente /: ID" sollte NICHT gespeichert werden.

Anders Gram Mygind
quelle

Antworten:

209

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, RouteReuseStrategydie Ihre Strategie in der implementiert und bereitstellt ngModule. Wenn Sie ändern möchten, wann die Route gespeichert ist, ändern Sie die shouldDetachFunktion. Bei der Rückkehr truespeichert Angular die Route. Wenn Sie ändern möchten, wenn die Route angehängt ist, ändern Sie die shouldAttachFunktion. 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.tsund legte sie zur /appsicheren 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 importes von reuse-strategy.tsallen 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 .

  1. Wenn Sie navigieren, wird shouldReuseRouteausgelö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.
  2. Wenn shouldReuseRoutezurückkommt false, wird shouldDetachausgelöst. shouldDetachLegt fest, ob Sie die Route speichern möchten oder nicht, und gibt eine entsprechende booleanAngabe 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 pathnicht im Array vorhanden ist.
  3. Wenn shouldDetachzurückgegeben wird true, storewird 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, DetachedRouteHandleda Angular dies verwendet, um Ihre gespeicherte Komponente später zu identifizieren. Unten speichere ich sowohl das DetachedRouteHandleals auch das ActivatedRouteSnapshotin 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?

  1. Nach shouldReuseRouteder Rückkehr falsewird erneut ausgeführt. shouldAttachDies 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!
  2. Nun Angular Sie werden fragen, „welche Komponente möchten Sie uns benutzen?“, Die Sie durch Rücksendung dieser Komponente anzeigt DetachedRouteHandleaus retrieve.

Das ist so ziemlich die ganze Logik, die Sie brauchen! Im folgenden Code für reuse-strategy.tshabe ich Ihnen auch eine raffinierte Funktion hinterlassen, mit der zwei Objekte verglichen werden. Ich benutze es, um die zukünftigen Routen route.paramsund route.queryParamsdie 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 shouldDetachMethode. 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 shouldDetachMethode ä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 shouldDetachwir das route.routeConfig.pathmit 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/:termund 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

Corbfon
quelle
2
@shaahin Ich habe ein Beispiel hinzugefügt, das der genaue Code ist, der in meiner aktuellen Implementierung enthalten ist!
Corbfon
1
@Corbfon Ich habe auch eine Ausgabe auf der offiziellen Github-Seite geöffnet: github.com/angular/angular/issues/13869
Tjaart van der Walt
2
Gibt es eine Möglichkeit, Eingabeanimationen erneut auszuführen, wenn eine gespeicherte Route erneut aktiviert wird?
Jinder Sidhu
2
Die ReuseRouteStrategy gibt Ihre Komponente an den Router zurück, sodass sie sich in dem Zustand befindet, in dem sie noch vorhanden ist. Wenn Sie möchten, dass die Komponente (n) auf den Anhang reagiert , können Sie einen Dienst verwenden, der eine Observable. Die Komponente sollte Observableden ngOnInitHook während des Lebenszyklus abonnieren . Dann können Sie der Komponente anhand von mitteilen ReuseRouteStrategy, dass sie gerade angehängt wurde, und die Komponente kann ihren Status entsprechend anpassen.
Corbfon
1
@AndersGramMygind Wenn meine Antwort eine Antwort auf die von Ihnen vorgeschlagene Frage liefert, würden Sie sie als Antwort markieren?
Corbfon
44

Lassen Sie sich von der akzeptierten Antwort nicht einschüchtern, dies ist ziemlich einfach. Hier ist eine schnelle Antwort, was Sie brauchen. Ich würde empfehlen, zumindest die akzeptierte Antwort zu lesen, da sie sehr detailliert ist.

Diese Lösung führt keinen Parametervergleich wie die akzeptierte Antwort durch, funktioniert jedoch gut zum Speichern einer Reihe von Routen.

app.module.ts importiert:

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;
 }
}
Chris Fremgen
quelle
1
Funktioniert dies auch für Routen, die faul geladen sind?
bluePearl
@bluePearl Überprüfen Sie die Antwort unten
Chris Fremgen
2
routeConfig ist null für verschiedene Routen, daher sollte shouldReuseRoute immer true zurückgeben, was nicht das gewünschte Verhalten ist
Gil Epshtain
19

Zusätzlich zu der akzeptierten Antwort (von Corbfon) und der kürzeren und einfacheren Erklärung von Chris Fremgen möchte ich eine flexiblere Art der Handhabung von Routen hinzufügen, die die Wiederverwendungsstrategie verwenden sollten.

Beide Antworten speichern die Routen, die zwischengespeichert werden sollen, in einem Array und prüfen dann, ob sich der aktuelle Routenpfad im Array befindet oder nicht. Diese Prüfung erfolgt shouldDetachmethodisch.

Ich finde diesen Ansatz unflexibel, denn wenn wir den Namen der Route ändern möchten, müssen wir daran denken, auch den Routennamen in unserer CustomReuseStrategyKlasse zu ändern . Wir können entweder vergessen, es zu ändern, oder ein anderer Entwickler in unserem Team kann beschließen, den Routennamen zu ändern, ohne zu wissen, ob es ihn gibt RouteReuseStrategy.

Anstatt die Routen, die wir zwischenspeichern möchten, in einem Array zu speichern, können wir sie direkt RouterModulemit dataobject markieren . Auf diese Weise wird die Wiederverwendungsstrategie auch dann angewendet, wenn wir den Routennamen ändern.

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

Und dann shouldDetachmachen wir in der Methode davon Gebrauch.

shouldDetach(route: ActivatedRouteSnapshot): boolean {
  return route.data.reuseRoute === true;
}
Davor
quelle
1
Gute Lösung. Dies sollte wirklich nur mit einem einfachen Flag, wie Sie es angewendet haben, in die Standardstrategie zur Wiederverwendung von Winkelrouten integriert werden.
MIP1983
Gute Antwort. Vielen Dank!
claudiomatiasrg
14

Ändern Sie die CustomReuseStrategy-Klasse wie folgt, um die Strategie von Chris Fremgen mit träge geladenen Modulen zu verwenden:

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;
  }
}

Definieren Sie schließlich in den Routing-Dateien Ihrer Feature-Module Ihre Schlüssel:

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

Mehr Infos hier .

Uğur Dinç
quelle
1
Vielen Dank für das Hinzufügen! Ich muss es versuchen. Es könnte sogar einige der Probleme bei der Handhabung untergeordneter Routen beheben, auf die meine Lösung stößt.
Corbfon
Ich musste verwenden route.data["key"], um ohne Fehler zu bauen. Aber das Problem, das ich habe, ist, dass ich eine Route + -Komponente habe, die an zwei verschiedenen Orten verwendet wird. 1. sample/list/itemund 2. product/id/sample/list/itemwenn ich zum ersten Mal einen der Pfade lade, wird er gut geladen, aber der andere löst den erneut angehängten Fehler aus, da ich basierend auf speichere. list/itemMeine Arbeit besteht also darin, die Route zu duplizieren und einige Änderungen am URL-Pfad vorzunehmen, aber dieselbe Komponente anzuzeigen. Ich bin mir nicht sicher, ob es dafür eine andere Lösung gibt.
bluePearl
Diese Art verwirrte mich, das oben genannte würde einfach nicht funktionieren, es würde explodieren, sobald ich eine meiner Cache-Routen traf (es würde nicht mehr navigieren und dort, wo Fehler in der Konsole waren). Chris Fremgens Lösung scheint mit meinen faulen Modulen gut zu funktionieren, soweit ich das
beurteilen
11

Eine weitere Implementierung ist gültiger, vollständiger und wiederverwendbarer. Dieser unterstützt faul geladene Module als @ Uğur Dinç und integriert das @ Davor-Routendatenflag. Die beste Verbesserung ist die automatische Generierung einer (fast) eindeutigen Kennung basierend auf dem absoluten Seitenpfad. Auf diese Weise müssen Sie es nicht auf jeder Seite selbst definieren.

Markieren Sie jede Seite, die Sie zwischenspeichern möchten reuseRoute: true. Es wird in der shouldDetachMethode verwendet.

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

Dies ist die einfachste Strategieimplementierung, ohne Abfrageparameter zu vergleichen.

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('/');
  }
}

Dieser vergleicht auch die Abfrageparameter. compareObjectshat eine kleine Verbesserung gegenüber der @ Cortfon-Version: Durchlaufen Sie die Eigenschaften von Basis- und Vergleichsobjekten. Denken Sie daran, dass Sie eine externe und zuverlässigere Implementierung wie die lodash- isEqualMethode verwenden können.

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;
  }
}

Wenn Sie die beste Möglichkeit haben, eindeutige Schlüssel zu generieren, kommentieren Sie meine Antwort. Ich werde den Code aktualisieren.

Vielen Dank an alle, die ihre Lösung geteilt haben.

McGiogen
quelle
3
Dies sollte die akzeptierte Antwort sein. Viele der oben bereitgestellten Lösungen können nicht mehrere Seiten mit derselben untergeordneten URL unterstützen. Weil sie die aktivierte Route-URL vergleichen, die nicht der vollständige Pfad ist.
zhuhang.jasper
4

Alle genannten Lösungen waren in unserem Fall irgendwie unzureichend. Wir haben eine kleinere Business-App mit:

  1. Einführungsseite
  2. Loginseite
  3. App (nach dem Login)

Unsere Anforderungen:

  1. Faul geladene Module
  2. Mehrstufige Routen
  3. Speichern Sie alle Router- / Komponentenzustände im Speicher im App-Bereich
  4. Option zur Verwendung der Standardstrategie zur Wiederverwendung von Winkeln auf bestimmten Routen
  5. Zerstörung aller im Speicher gespeicherten Komponenten beim Abmelden

Vereinfachtes Beispiel unserer Routen:

const routes: Routes = [{
    path: '',
    children: [
        {
            path: '',
            canActivate: [CanActivate],
            loadChildren: () => import('./modules/dashboard/dashboard.module').then(module => module.DashboardModule)
        },
        {
            path: 'companies',
            canActivate: [CanActivate],
            loadChildren: () => import('./modules/company/company.module').then(module => module.CompanyModule)
        }
    ]
},
{
    path: 'login',
    loadChildren: () => import('./modules/login/login.module').then(module => module.LoginModule),
    data: {
        defaultReuseStrategy: true, // Ignore our custom route strategy
        resetReuseStrategy: true // Logout redirect user to login and all data are destroyed
    }
}];

Wiederverwendungsstrategie:

export class AppReuseStrategy implements RouteReuseStrategy {

private handles: Map<string, DetachedRouteHandle> = new Map();

// Asks if a snapshot from the current routing can be used for the future routing.
public shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    return future.routeConfig === curr.routeConfig;
}

// Asks if a snapshot for the current route already has been stored.
// Return true, if handles map contains the right snapshot and the router should re-attach this snapshot to the routing.
public shouldAttach(route: ActivatedRouteSnapshot): boolean {
    if (this.shouldResetReuseStrategy(route)) {
        this.deactivateAllHandles();
        return false;
    }

    if (this.shouldIgnoreReuseStrategy(route)) {
        return false;
    }

    return this.handles.has(this.getKey(route));
}

// Load the snapshot from storage. It's only called, if the shouldAttach-method returned true.
public retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
    return this.handles.get(this.getKey(route)) || null;
}

// Asks if the snapshot should be detached from the router.
// That means that the router will no longer handle this snapshot after it has been stored by calling the store-method.
public shouldDetach(route: ActivatedRouteSnapshot): boolean {
    return !this.shouldIgnoreReuseStrategy(route);
}

// After the router has asked by using the shouldDetach-method and it returned true, the store-method is called (not immediately but some time later).
public store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle | null): void {
    if (!handle) {
        return;
    }

    this.handles.set(this.getKey(route), handle);
}

private shouldResetReuseStrategy(route: ActivatedRouteSnapshot): boolean {
    let snapshot: ActivatedRouteSnapshot = route;

    while (snapshot.children && snapshot.children.length) {
        snapshot = snapshot.children[0];
    }

    return snapshot.data && snapshot.data.resetReuseStrategy;
}

private shouldIgnoreReuseStrategy(route: ActivatedRouteSnapshot): boolean {
    return route.data && route.data.defaultReuseStrategy;
}

private deactivateAllHandles(): void {
    this.handles.forEach((handle: DetachedRouteHandle) => this.destroyComponent(handle));
    this.handles.clear();
}

private destroyComponent(handle: DetachedRouteHandle): void {
    const componentRef: ComponentRef<any> = handle['componentRef'];

    if (componentRef) {
        componentRef.destroy();
    }
}

private getKey(route: ActivatedRouteSnapshot): string {
    return route.pathFromRoot
        .map((snapshot: ActivatedRouteSnapshot) => snapshot.routeConfig ? snapshot.routeConfig.path : '')
        .filter((path: string) => path.length > 0)
        .join('');
    }
}
Hovado
quelle
3

Das Folgende ist Arbeit! Referenz: 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, '_')
    }
}

红兵 伍
quelle
1
Vorsicht, dies verwendet eine interne Variable _routerState.
DarkNeuron
@ DarkNeuron _routerStateverursacht irgendwelche schädlichen?
k11k2
2
Nein, aber Google ist nicht verpflichtet, diese Variable beizubehalten, da sie intern verwendet und nicht in der API verfügbar gemacht wird.
DarkNeuron
wenn wir anrufen deleteRouteSnapshot?
k11k2
0

Ich war mit folgenden Problemen konfrontiert, die eine benutzerdefinierte Strategie zur Wiederverwendung von Routen implementierten:

  1. Führen Sie Vorgänge für das Anhängen / Löschen von Routen aus: Verwalten von Abonnements, Bereinigen usw.;
  2. Behalten Sie nur den Status der zuletzt parametrisierten Route bei: Speicheroptimierung;
  3. Wiederverwenden einer Komponente, nicht eines Status: Verwalten Sie den Status mit einem Tool zur Statusverwaltung.
  4. Fehler "ActivatedRouteSnapshot kann nicht erneut von einer anderen Route erstellt werden" Fehler;

Also schrieb ich eine Bibliothek, um diese Probleme zu lösen. Die Bibliothek bietet einen Dienst und Dekoratoren zum Anhängen / Trennen von Hooks und verwendet die Komponenten einer Route zum Speichern getrennter Routen, nicht der Pfade einer Route.

Beispiel:

/* Usage with decorators */
@onAttach()
public onAttach(): void {
  // your code...
}

@onDetach()
public onDetach(): void {
  // your code...
}

/* Usage with a service */
public ngOnInit(): void {
  this.cacheRouteReuse
    .onAttach(HomeComponent) // or any route's component
    .subscribe(component => {
      // your code...
    });

  this.cacheRouteReuse
    .onDetach(HomeComponent) // or any route's component
    .subscribe(component => {
      // your code...
    });
}

Die Bibliothek: https://www.npmjs.com/package/ng-cache-route-reuse

Stas Amasev
quelle
Nur eine Verknüpfung zu Ihrer eigenen Bibliothek oder Ihrem eigenen Tutorial ist keine gute Antwort. Wenn Sie darauf verlinken, erklären, warum das Problem dadurch gelöst wird, Code dazu bereitstellen und nicht angeben, dass Sie es geschrieben haben, erhalten Sie eine bessere Antwort. Siehe: Was bedeutet „gute“ Eigenwerbung?
Paul Roub