Jak zaimplementować Routereusestrategia powinna być dostosowana do konkretnych tras w Angular 2

Mam moduł Angular 2, w którym zaimplementowałem routing i chciałbym zapisać Stany podczas nawigacji. Użytkownik powinien mieć możliwość: 1. wyszukiwanie dokumentów za pomocą searchformula 2. przejdź do jednego z wyników 3. przejdź z powrotem do searchresult-bez komunikacji z serwerem

Jest to możliwe w tym RouteReuseStrategy. Pytanie brzmi:: Jak zaimplementować, aby dokument nie był przechowywany?

Więc stan ścieżki "dokumenty" powinien być przechowywane i ścieżka trasy "documents/:id"' stan nie powinien być przechowywany?

Author: Corbfon, 2016-12-22

4 answers

Hej Anders, świetne pytanie!

Mam prawie taki sam przypadek użycia jak ty, i chciałem zrobić to samo! Wyszukiwanie użytkowników > uzyskaj wyniki > użytkownik nawiguje do wyniku > użytkownik nawiguje z powrotem > BOOM błyskawiczny powrót do wyników, ale nie chcesz zapisywać konkretnego wyniku, do którego nawigował użytkownik.

Tl; dr

Musisz mieć klasę, która implementuje RouteReuseStrategy i przedstawia swoją strategię w ngModule. Jeśli chcesz zmodyfikować kiedy trasa jest zapisana, zmodyfikuj funkcję shouldDetach. Gdy zwraca true, Angular zapisuje trasę. Jeśli chcesz zmodyfikować kiedy trasa jest dołączona, zmodyfikuj funkcję shouldAttach. Gdy shouldAttach zwróci true, Angular użyje zapisanej trasy zamiast żądanej trasy. To jest Plunker dla Ciebie do zabawy.

O RouteReuseStrategy

Zadając to pytanie, już rozumiesz, że RouteReuseStrategy pozwala ci powiedzieć Angular2 Nie , aby zniszczyć komponent, ale w rzeczywistości, aby zapisać go do ponownego renderowania w późniejszym terminie. Fajne bo pozwala:

  • zmniejszone wywołania serwera
  • zwiększona prędkość
  • i komponent domyślnie renderuje w tym samym stanie, w jakim został pozostawiony

Ten ostatni jest ważny, jeśli chcesz, powiedzmy, tymczasowo opuścić stronę, mimo że użytkownik wprowadził do niej partię tekstu. Przedsiębiorstwo aplikacje pokochają tę funkcję ze względu na nadmierną Ilość formularzy!

To jest to, co wymyśliłem, aby rozwiązać problem. Jak powiedziałeś, musisz skorzystać z RouteReuseStrategy oferowanego przez @ angular / router w wersjach 3.4.1 i wyższych.

TODO

Najpierw upewnij się, że twój projekt ma @angular / router w wersji 3.4.1 lub wyższej.

Następnie , Utwórz plik, w którym będzie przechowywana twoja klasa, która implementuje RouteReuseStrategy. Nazwałem swoją reuse-strategy.ts i umieścił go w folderze /app do przechowania. Na razie klasa ta powinna wyglądać następująco:

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

export class CustomReuseStrategy implements RouteReuseStrategy {
}

(nie martw się o błędy maszynopisu, zaraz wszystko rozwiążemy)

Zakończ prace fundamentowe dostarczając klasę do twojego app.module. Zauważ, że nie napisałeś jeszcze CustomReuseStrategy, ale powinieneś iść naprzód i import to z reuse-strategy.ts wszystko to samo. Również import { RouteReuseStrategy } from '@angular/router';

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

Ostatnim elementem jest napisanie klasy, która będzie kontrolować, czy trasy są odłączane, przechowywane, odzyskiwane i ponownie dołączane. Zanim przejdziemy do starego kopiuj / wklej, zrobię krótkie wyjaśnienie mechaniki tutaj, jak je Rozumiem. Proszę zapoznać się z poniższym kodem dla metod, które opisuję, i oczywiście w kodzie jest mnóstwo dokumentacji .

  1. podczas nawigacji, shouldReuseRoute odpala. Ta jest dla mnie trochę dziwna, ale jeśli zwróci true, to faktycznie ponownie wykorzysta trasę, na której aktualnie jesteś, a żadna z innych metod są zwolnione. Po prostu zwracam false, jeśli użytkownik nawiguje.
  2. If shouldReuseRoute zwraca false, shouldDetach pożary. shouldDetach określa, czy chcesz zapisać trasę, czy nie, i zwraca boolean wskazując tyle. tutaj powinieneś zdecydować się na przechowywanie / nie zapisywanie ścieżek , co zrobiłbym, sprawdzając tablicę ścieżek, które chcesz przechowywaną dla route.routeConfig.path, i zwracając false, jeśli path nie istnieje w tablicy.
  3. Jeśli shouldDetach zwróci true, store jest zwolniony, co jest dla Ciebie okazją do przechowywania wszelkich informacji na temat trasy. Cokolwiek zrobisz, będziesz musiał przechowywać DetachedRouteHandle, ponieważ to właśnie Angular używa do identyfikacji przechowywanego komponentu później. Poniżej zapisuję zarówno DetachedRouteHandle, jak i ActivatedRouteSnapshot do zmiennej lokalnej mojej klasy.

Więc, widzieliśmy logikę przechowywania, ale co z nawigacją do komponentu? W jaki sposób Angular decyduje się przechwycić nawigację i umieścić zapisaną w jej miejsce.

  1. po powrocie shouldReuseRoute false, shouldAttach działa, czyli masz szansę dowiedzieć się, czy chcesz zregenerować lub użyć komponentu w pamięci. Jeśli chcesz ponownie użyć przechowywanego komponentu, zwróć true i jesteś na dobrej drodze!
  2. teraz Angular zapyta cię: "który komponent mamy użyć?", które wskażesz zwracając DetachedRouteHandle tego komponentu z retrieve.
To wszystko czego potrzebujesz! W kodzie dla reuse-strategy.ts, poniżej zostawiłem Ci również sprytną funkcję, która porównuje dwa obiekty. Używam go do porównania przyszłych tras route.params i route.queryParams z zapisanymi. jeśli wszystkie pasują do siebie, Chcę użyć zapisanego komponentu zamiast generować nowy. Ale jak to zrobisz, zależy od Ciebie!

Strategia ponownego wykorzystania.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;
    }
}

Zachowanie

Ta implementacja przechowuje każdą unikalną trasę, którą użytkownik odwiedza na routerze dokładnie raz. To będzie kontynuuj dodawanie do komponentów przechowywanych w pamięci przez całą sesję użytkownika na stronie. Jeśli chcesz ograniczyć trasy, które przechowujesz, miejscem do tego jest metoda shouldDetach. Kontroluje, które trasy zapisujesz.

Przykład

Powiedz, że twój użytkownik szuka czegoś ze strony głównej, która nawiguje do ścieżki search/:term, która może wyglądać jak www.yourwebsite.com/search/thingsearchedfor. Strona wyszukiwania zawiera kilka wyników wyszukiwania. Chcesz zapisać tę trasę, na wypadek, gdyby chcieli wracaj do tego! Teraz klikają wynik wyszukiwania i przechodzą do view/:resultId, którego nie chcesz przechowywać, ponieważ prawdopodobnie będą tam tylko raz. Z powyższą implementacją po prostu zmieniłbym metodę shouldDetach! Oto jak to może wyglądać:

Po pierwsze zróbmy tablicę ścieżek, które chcemy przechowywać.

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

Teraz, w shouldDetach możemy sprawdzić route.routeConfig.path na naszej tablicy.

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

Ponieważ Angular będzie przechowywać tylko jedna instancja trasy, ta pamięć będzie lekka i będziemy przechowywać tylko komponent znajdujący się w search/:term, a nie wszystkie inne!

Dodatkowe Linki

Chociaż nie ma jeszcze zbyt wiele dokumentacji, oto kilka linków do tego, co istnieje:

Https://angular.io/docs/ts/latest/api/router/index/RouteReuseStrategy-class.html

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

 124
Author: Corbfon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-23 01:26:21

Nie daj się zastraszyć zaakceptowaną odpowiedzią, jest to dość proste. Oto szybka odpowiedź, czego potrzebujesz. Polecam przynajmniej przeczytanie zaakceptowanej odpowiedzi, ponieważ jest pełna szczegółów.

To rozwiązanie nie robi żadnego porównania parametrów, jak przyjęta odpowiedź, ale będzie działać dobrze dla przechowywania zestawu tras.

App.moduł.TS import:

import { RouteReuseStrategy } from '@angular/router';
import { CustomReuseStrategy, Routing } from './shared/routing';

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

Dzielone / 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;
 }
}
 18
Author: Chris Fremgen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-30 20:46:49

Aby użyć strategii Chrisa Fremgena z leniwie załadowanymi modułami, zmodyfikuj klasę CustomReuseStrategy do następującej:

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

Na koniec, w plikach routingu modułów funkcji zdefiniuj swoje klucze:

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

Więcej informacji tutaj .

 7
Author: dexter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-27 18:08:08

Oto praca! numer referencyjny: 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, '_')
    }
}
 2
Author: 红兵伍,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-09 15:37:57