Як реалізувати RouteReuseStrategy слідDetach для конкретних маршрутів у Angular 2


114

У мене є модуль Angular 2, в якому я реалізував маршрутизацію і хотів би, щоб стани зберігалися під час навігації. Користувач повинен мати можливість: 1. шукати документи за допомогою формули пошуку 2. переходити до одного з результатів 3. повертатися до результату пошуку - без спілкування з сервером

Це можливо, включаючи RouteReuseStrategy. Питання таке: як мені реалізувати, що документ не повинен зберігатися?

Отже, стан "документа" маршруту маршруту повинен зберігатися, а стан маршруту "документи /: id" 'НЕ слід зберігати?

Відповіді:


209

Гей Андерс, чудове запитання!

У мене майже такий самий випадок використання, що і у вас, і я хотів зробити те саме! Пошук користувачів> отримання результатів> Користувач переходить до результату> Користувач повертається назад> БУМ, що запалює, швидко повертається до результатів , але ви не хочете зберігати конкретний результат, до якого користувач перейшов.

тл; д-р

Потрібно мати клас, який реалізує RouteReuseStrategyта надає свою стратегію в ngModule. Якщо ви хочете змінити, коли маршрут зберігається, змініть shouldDetachфункцію. Коли він повертається true, Angular зберігає маршрут. Якщо ви хочете змінити, коли маршрут додається, змініть shouldAttachфункцію. Коли shouldAttachповернеться вірно, Angular буде використовувати збережений маршрут замість запитуваного маршруту. Ось вам Plunker, з яким ви можете пограти.

Про RouteReuseStrategy

Задавши це запитання, ви вже розумієте, що RouteReuseStrategy дозволяє сказати Angular не знищувати компонент, а насправді зберегти його для повторного візуалізації в більш пізній термін. Це здорово, тому що він дозволяє:

  • Зменшення кількості дзвінків на сервері
  • Підвищена швидкість
  • І компонент переводить за замовчуванням у той самий стан, який він залишився

Останнє є важливим, якщо ви хочете, скажімо, залишити сторінку тимчасово, навіть якщо користувач ввів у неї багато тексту. Підприємницькі програми будуть любити цю функцію через надмірну кількість форм!

Це те, що я придумав, щоб вирішити проблему. Як ви сказали, вам потрібно скористатисяRouteReuseStrategy запропонованим @ angular / router версією 3.4.1 і вище.

РОБИТИ

Перший переконайтеся, що у вашого проекту @ кутова / маршрутизатор версії 3.4.1 або новішої.

Далі створіть файл, в якому буде розміщений ваш клас, який реалізується RouteReuseStrategy. Я зателефонував моєму reuse-strategy.tsі помістив його в /appпапку для зберігання. Наразі цей клас повинен виглядати так:

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

export class CustomReuseStrategy implements RouteReuseStrategy {
}

(не хвилюйтеся про ваші помилки TypeScript. Ми все вирішимо)

Закінчіть основу , надавши клас своєму app.module. Зауважте, що ви ще не написали CustomReuseStrategy, але слід продовжувати importце і все з reuse-strategy.tsтого ж. Такожimport { RouteReuseStrategy } from '@angular/router';

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

Останній фрагмент - це написання класу, який контролюватиме, чи слід маршрути від'єднувати, зберігати, витягувати і повторно приєднувати. Перш ніж ми перейдемо до старої копії / вставки , я пройду тут коротке пояснення механіки, наскільки я їх розумію. Посилайтеся на код нижче щодо методів, які я описую, і, звичайно, в коді є багато документації .

  1. Коли ви орієнтуєтесь, shouldReuseRouteспрацьовує. Це мені трохи дивно, але якщо він повернетьсяtrue , він фактично повторно використовує маршрут, на якому ви зараз перебуваєте, і жоден з інших методів не запускається. Я просто повертаю помилкове, якщо користувач пересувається.
  2. Якщо shouldReuseRouteповертається false, shouldDetachпожежі. shouldDetachвизначає, чи хочете ви зберігати маршрут, чи повертає booleanвказівник стільки ж. Тут ви повинні вирішити зберігати / не зберігати шляхи , що я б зробив, перевіривши масив шляхів, до яких потрібно зберегти route.routeConfig.path, і повернути помилкове, якщоpath в масиві не існує.
  3. Якщо shouldDetachповертається true, storeобпалюють, який є можливість зберігати будь-яку інформацію ви хотіли б про маршрут. Що б ви не робили, вам потрібно буде зберігати, DetachedRouteHandleтому що саме Angular використовує, щоб згодом ідентифікувати ваш збережений компонент. Нижче я зберігаю як DetachedRouteHandleі ActivatedRouteSnapshotперемінну локальну для мого класу.

Отже, ми бачили логіку зберігання, а як щодо переходу до компонента? Як Angular вирішує перехопити вашу навігацію та поставити збережену на її місце?

  1. Знову ж таки, після того, як shouldReuseRouteповернувся false, shouldAttachзапускається, що є вашим шансом з’ясувати, чи хочете ви відновити чи використовувати компонент у пам'яті. Якщо ви хочете повторно використовувати збережений компонент, поверніться trueі ви вже на шляху!
  2. Тепер Angular запитає вас, "який компонент ви хочете, щоб ми використовували?", Який ви вкажете, повернувши цей компонент DetachedRouteHandleз retrieve.

Це майже вся логіка, яка вам потрібна! У коді reuse-strategy.tsнижче, я також залишив вам чудову функцію, яка буде порівнювати два об'єкти. Я використовую його для порівняння майбутнього маршруту route.paramsта route.queryParamsіз збереженим. Якщо всі вони збігаються, я хочу використовувати збережений компонент, а не генерувати новий. Але як ви це зробите, залежить від вас!

повторне використання-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;
    }
}

Поведінка

Ця реалізація зберігає кожен унікальний маршрут, який користувач відвідує на маршрутизаторі рівно один раз. Це продовжуватиме додавати до компонентів, що зберігаються в пам'яті протягом усього сеансу користувача на сайті. Якщо ви хочете обмежити маршрути, які ви зберігаєте, місце це зробити - це shouldDetachметод. Він контролює, які маршрути ви збережете.

Приклад

Скажіть, що ваш користувач шукає щось з домашньої сторінки, що перенаправляє їх до шляху search/:term, що може виглядати так www.yourwebsite.com/search/thingsearchedfor. Сторінка пошуку містить купу результатів пошуку. Ви хочете зберегти цей маршрут, якщо вони захочуть повернутися до нього! Тепер вони натискають на результат пошуку і переходять до них view/:resultId, які ви не хочете зберігати, бачачи, що вони, ймовірно, будуть там лише один раз. Маючи вищезазначене впровадження, я просто змінив би shouldDetachметод! Ось як це може виглядати:

Спочатку давайте зробимо масив шляхів, які ми хочемо зберегти.

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

Тепер shouldDetachми можемо перевірити route.routeConfig.pathпроти нашого масиву.

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

Оскільки Angular зберігатиме лише один екземпляр маршруту, це сховище буде легким, і ми будемо зберігати лише компонент, розташований у, search/:termа не всі інші!

Додаткові посилання

Хоча там ще мало документації, ось кілька посилань на те, що існує:

Кутові документи: https://angular.io/docs/ts/latest/api/router/index/RouteReuseStrategy-class.html

Стаття вступу: https://www.softwarearchitekt.at/post/2016/12/02/sticky-routes-in-angular-2-3-with-routereusestrategy.aspx

Реалізація RouteReuseStrategy за замовчуванням nativescript-angular : https://github.com/NativeScript/nativescript-angular/blob/cb4fd3a/nativescript-angular/router/ns-route-reuse-strategy.ts


2
@shaahin Я додав приклад, який є точним кодом, що міститься в моїй поточній реалізації!
Корбфон

1
@Corbfon Я також відкрив випуск на офіційній сторінці github: github.com/angular/angular/isissue/13869
Tjaart van der Walt

2
Чи є спосіб змусити його повторно запустити анімаційні фільми при повторній активації збереженого маршруту?
Джіндер Сидху

2
ReuseRouteStrategy поверне ваш компонент назад до маршрутизатора, тому він буде знаходитися в будь-якому стані, в якому він був залишений. Якщо ви хочете, щоб компоненти (-и) реагували на вкладення, ви можете скористатися послугою, яка пропонує Observable. Компонент повинен підписатися на цикл Observableпід час ngOnInitжиттєвого циклу. Тоді ви зможете сказати компоненту з-за того ReuseRouteStrategy, що він тільки що був приєднаний, і компонент може змінити його стан як підходить.
Корбфон

1
@AndersGramMygind, якщо моя відповідь дає відповідь на запропоноване вами питання, чи позначаєте ви це як відповідь?
Корбфон

45

Не лякайтеся прийнятою відповіддю, це досить просто. Ось швидка відповідь, що вам потрібно. Я б рекомендував хоча б прочитати прийняту відповідь, оскільки вона наповнена великими деталями.

Це рішення не проводить порівняння параметрів, як прийнята відповідь, але воно буде добре працювати для зберігання набору маршрутів.

імпорт app.module.ts:

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

1
Це також буде працювати для маршрутів, які ледаче завантажені?
bluePearl

@bluePearl Перевір відповідь нижче
Chris Fremgen

2
routeConfig є недійсним для різних маршрутів, тому слідReuseRoute завжди повертатиметься справжнє, що не є бажаним поведінкою
Gil Epshtain,

19

На додаток до прийнятої відповіді (Корбфоном) та більш короткого та прямого пояснення Кріса Фремгена, я хочу додати більш гнучкий спосіб керування маршрутами, який повинен використовувати стратегію повторного використання.

Обидва відповіді зберігають маршрути, які ми хочемо кешувати в масиві, а потім перевіряємо, чи є поточний шлях маршруту в масиві чи ні. Ця перевірка робиться shouldDetachметодом.

Я вважаю такий підхід негнучким, тому що якщо ми хочемо змінити назву маршруту, нам слід пам’ятати, щоб також змінити назву маршруту в нашому CustomReuseStrategyкласі. Ми можемо або забути його змінити, або інший розробник нашої команди може вирішити змінити назву маршруту, навіть не знаючи про існування RouteReuseStrategy.

Замість того, щоб зберігати маршрути, які ми хочемо кешувати в масиві, ми можемо позначити їх безпосередньо за RouterModuleдопомогою dataоб'єкта. Таким чином, навіть якщо ми змінимо назву маршруту, стратегія повторного використання все одно застосовуватиметься.

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

І тоді в shouldDetachметоді ми використовуємо це.

shouldDetach(route: ActivatedRouteSnapshot): boolean {
  return route.data.reuseRoute === true;
}

1
Гарне рішення. Це дійсно повинно бути зафіксовано у стандартній стратегії повторного використання кутового маршруту простим прапором, як ви застосували.
MIP1983

Чудова відповідь. Велике спасибі!
claudiomatiasrg

14

Щоб використовувати стратегію Кріса Фремгена з ліниво завантаженими модулями, змініть клас CustomReuseStrategy на наступне:

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

нарешті, у файлах маршрутизації модулів функцій визначте свої ключі:

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

Більше інформації тут .


1
Дякуємо, що додали це! Я мушу спробувати. Це навіть могло б виправити деякі проблеми, пов’язані з обробкою маршруту, з якими стикається моє рішення.
Корбфон

Мені довелося використовувати, route.data["key"]щоб будувати без помилок. Але проблема, яка у мене виникає, полягає в тому, що у мене є компонент route +, який використовується в двох різних місцях. 1. sample/list/itemі 2. product/id/sample/list/itemколи я вперше завантажую будь-який із контурів, він завантажує чудово, але інший кидає повторно встановлену помилку, тому що я зберігаю на основі list/itemОтже, моя робота навколо я дублював маршрут і вніс певні зміни до шляху URL, але відображаючи той самий компонент. Не впевнений, чи є для цього ще одна робота.
bluePearl

Цей вид збентежив мене, вищезгадане просто не спрацювало, воно підірветься, як тільки я потрапляю на один із кешових маршрутів, (він більше не переходитиме і там, де помилки в консолі). Рішення Кріса Фремгена, здається, працює добре з моїми ледачими модулями, наскільки я можу сказати поки що ...
MIP1983,

12

Ще одна реалізація більш достовірна, повна та багаторазова. Цей підтримує ледачі завантажені модулі, як @ Uğur Dinç та інтегрує прапор даних маршруту @Davor. Найкраще вдосконалення - це автоматичне створення (майже) унікального ідентифікатора на основі абсолютного шляху сторінки. Таким чином, вам не потрібно визначати це самостійно на кожній сторінці.

Позначте будь-яку сторінку, яку ви хочете налаштувати в кеш reuseRoute: true. Він буде використаний у shouldDetachметоді.

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

Це найпростіша реалізація стратегії, не порівнюючи параметри запитів.

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

Цей також порівняємо параметри запитів. compareObjectsмає незначне покращення порівняно з версією @Corbfon: перегляньте властивості як базових, так і порівняйте об'єкти. Пам'ятайте, що ви можете використовувати зовнішню і більш надійну реалізацію, наприклад isEqualметод lodash .

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

Якщо у вас є найкращий спосіб генерувати унікальні клавіші, коментуючи мою відповідь, я оновлю код.

Дякую всім хлопцям, які поділилися своїм рішенням.


3
Це має бути прийнятою відповіддю. Багато запропонованих вище рішень не можуть підтримувати кілька сторінок з однією дочірньою URL-адресою. Тому що вони порівнюють URL-адресу activeRoute, що не повний шлях.
zhuhang.jasper

4

Усі згадані рішення були якось недостатніми в нашому випадку. У нас є менший бізнес-додаток із:

  1. Сторінка вступу
  2. Сторінка входу
  3. Додаток (після входу)

Наші вимоги:

  1. Ледачі завантажені модулі
  2. Багаторівневі маршрути
  3. Зберігайте всі стани маршрутизатора / компонента в пам'яті в розділі програми
  4. Можливість використання стратегії кутового повторного використання за замовчуванням на конкретних маршрутах
  5. Знищення всіх компонентів, що зберігаються в пам'яті під час виходу

Спрощений приклад наших маршрутів:

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

Стратегія повторного використання:

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

3

далі робота! довідка: 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, '_')
    }
}


1
Обережно, тут використовується внутрішня змінна _routerState.
DarkNeuron

@DarkNeuron чи _routerStateзавдає шкоди?
k11k2

2
Ні, але Google не зобов'язаний зберігати цю змінну навколо, оскільки вона використовується внутрішньо і не піддається впливу в API.
DarkNeuron

коли ми дзвонимо deleteRouteSnapshot?
k11k2

0

Я зіткнувся з цими проблемами, реалізуючи власну стратегію повторного використання маршруту:

  1. Виконуйте операції з вкладенням / детахом маршруту: керуйте підписками, очищенням тощо;
  2. Збережіть лише останній параметризований стан маршруту: оптимізація пам'яті;
  3. Використовуйте повторно компонент, а не стан: керуйте державою за допомогою інструментів управління державою.
  4. Помилка "Неможливо повторно встановити ActivateRouteSnapshot, створений з іншого маршруту";

Тому я написав бібліотеку, що вирішує ці питання. Бібліотека надає послугу та декоратори для кріплення / від'єднання гачків та використовує компоненти маршруту для зберігання відокремлених маршрутів, а не шляхів маршруту.

Приклад:

/* 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...
    });
}

Бібліотека: https://www.npmjs.com/package/ng-cache-route-reuse


Просто посилання на власну бібліотеку чи підручник не є гарною відповіддю. Посилання на нього, пояснення, чому він вирішує проблему, надання коду про те, як це зробити, і відмова від того, що ви його написали, дає кращу відповідь. Див.: Що означає "хороша" самореклама?
Пол Руб
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.