RouteReuseStrategy shouldDetach'i Angular 2'de belirli rotalar için uygulama


116

Yönlendirme uyguladığım ve gezinirken durumların depolanmasını istediğim bir Angular 2 modülüm var.
Kullanıcı şunları yapabilmelidir:

  1. 'arama formülü' kullanarak belgeleri arayın
  2. sonuçlardan birine git
  3. sunucuyla iletişim kurmadan 'arama sonucuna' geri dönün

Bu dahil mümkündür RouteReuseStrategy.
Soru şudur:
Belgenin saklanmaması gerektiğini nasıl uygulayabilirim?

Dolayısıyla yol yolu "belgeleri" durumu saklanmalı ve yol yolu "belgeler /: id" 'durumu KAYDEDİLMEMELİ?

Yanıtlar:


210

Hey Anders, harika soru!

Sizinle neredeyse aynı kullanım durumum var ve aynı şeyi yapmak istedim! Kullanıcı arama> sonuçları al> Kullanıcı sonuca gider> Kullanıcı geri döner> BOOM hızlı bir şekilde sonuçlara geri döner , ancak kullanıcının gittiği belirli sonucu saklamak istemezsiniz.

tl; Dr.

RouteReuseStrategyStratejinizi uygulayan ve sağlayan bir sınıfa sahip olmanız gerekir ngModule. Rota kaydedildiğinde değişiklik yapmak istiyorsanız, shouldDetachişlevi değiştirin . Döndüğünde true, Angular rotayı kaydeder. Rota eklendiğinde değişiklik yapmak istiyorsanız, shouldAttachişlevi değiştirin . Ne zaman shouldAttachtrue döndürür, Açısal istenen rotanın yerde saklanır rotayı kullanacaktır. İşte oynayabileceğiniz bir Plunker .

RouteReuseStrategy Hakkında

Bu soruyu sorduğunuzda, RouteReuseStrategy'nin Angular'a bir bileşeni yok etmemesini söylemenize , aslında onu daha sonraki bir tarihte yeniden oluşturmak için kaydetmenize izin verdiğini zaten anlıyorsunuz . Bu harika çünkü şunları sağlar:

  • Azalan sunucu çağrıları
  • Artan hız
  • VE bileşen varsayılan olarak bırakıldığı durumda işler.

Sonuncusu, örneğin, kullanıcı çok fazla metin girmiş olsa bile geçici olarak bir sayfadan ayrılmak istiyorsanız önemlidir . Kurumsal uygulamalar, aşırı sayıda form nedeniyle bu özelliğe bayılacak !

Sorunu çözmek için bulduğum şey bu. Söylediğiniz gibi, RouteReuseStrategy3.4.1 ve üzeri sürümlerde @ angular / router tarafından sunulanlardan yararlanmanız gerekiyor.

YAPMAK

İlk olarak projenizin @ angular / router sürüm 3.4.1 veya daha yüksek olduğundan emin olun.

Ardından , uygulayan sınıfınızı barındıracak bir dosya oluşturun RouteReuseStrategy. Benimkini aradım reuse-strategy.tsve /appsaklamak için klasöre yerleştirdim . Şimdilik bu sınıf şöyle görünmeli:

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

export class CustomReuseStrategy implements RouteReuseStrategy {
}

(TypeScript hatalarınız için endişelenmeyin, her şeyi çözmek üzereyiz)

Sınıfınızı sizin için sağlayarak temel çalışmalarını tamamlayınapp.module . Henüz yazmadım unutmayın CustomReuseStrategy, ama devam ve gitmeli importgelen o reuse-strategy.tshepsi aynı. Ayrıcaimport { RouteReuseStrategy } from '@angular/router';

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

Son parça , rotaların ayrılıp ayrılmayacağını, saklanıp saklanmayacağını, geri alınacağını ve yeniden bağlanıp bağlanmayacağını kontrol edecek sınıfı yazmaktır. Eski kopyala / yapıştır işlemine geçmeden önce , anladığım kadarıyla burada mekaniğin kısa bir açıklamasını yapacağım. Açıkladığım yöntemler için aşağıdaki kodu referans alın ve elbette kodda çok sayıda belge var .

  1. Gezinirken shouldReuseRouteateşler. Bu bana biraz tuhaf geliyor, ancak geri dönerse true, o zaman aslında şu anda bulunduğunuz rotayı yeniden kullanıyor ve diğer yöntemlerden hiçbiri ateşlenmiyor. Kullanıcı uzaklaşıyorsa sadece yanlış döndürürüm.
  2. Eğer shouldReuseRoutedöner false, shouldDetachyangınlar. shouldDetachrotayı saklamak isteyip istemediğinizi belirler ve bunu booleankadar gösteren bir değer döndürür . Yolları saklamaya / saklamamaya karar vermeniz gereken yer burasıdır , buna karşı depolanmasını istediğiniz bir dizi yolu kontrol ederek ve dizide route.routeConfig.pathyoksa yanlış döndürerek pathyapacağım.
  3. Geri shouldDetachdönülürse true, storeateşlenir, bu, rota hakkında istediğiniz bilgileri kaydetmeniz için bir fırsattır. Ne yaparsanız yapın, saklamanız gerekecek DetachedRouteHandleçünkü Angular daha sonra depolanan bileşeninizi tanımlamak için bunu kullanır. Aşağıda, hem depolamak DetachedRouteHandleve ActivatedRouteSnapshotdersime değişken yerel içine.

Demek ki gezinme hakkında depolama için mantığı, ama ne gördüm için bir bileşeni? Angular, navigasyonunuzu durdurmaya ve kayıtlı olanı yerine koymaya nasıl karar veriyor?

  1. Yine, sonra shouldReuseRoutegeri döndü false, shouldAttachsen yeniden veya bellekte bileşeni kullanmak isteyip anlamaya şans olan ishal. Depolanan bir bileşeni yeniden kullanmak istiyorsanız, geri dönün trueve yolunuza devam edin!
  2. Bunu bileşenin dönerek gösterecektir olan "bize kullanmak istiyorsunuz hangi bileşenin?" Soracaktır Şimdi Açısal DetachedRouteHandledan retrieve.

İhtiyacınız olan mantık hemen hemen bu! Aşağıdaki kodda reuse-strategy.ts, size iki nesneyi karşılaştıracak şık bir işlev de bıraktım. Gelecekteki rotaları route.paramsve route.queryParamskayıtlı olanları karşılaştırmak için kullanıyorum . Bunların hepsi eşleşirse, yeni bir tane oluşturmak yerine depolanan bileşeni kullanmak istiyorum. Ama bunu nasıl yapacağınız size kalmış!

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

davranış

Bu uygulama, kullanıcının yönlendiricide tam olarak bir kez ziyaret ettiği her benzersiz yolu depolar. Bu, kullanıcının sitedeki oturumu boyunca bellekte depolanan bileşenlere eklenmeye devam edecektir. Sakladığınız rotaları sınırlandırmak istiyorsanız, bunun yapılacağı yer shouldDetachyöntemdir. Hangi rotaları kaydettiğinizi kontrol eder.

Misal

Kullanıcınızın ana sayfadan bir şey aradığını ve onu yola yönlendiren search/:termve şöyle görünebileceğini varsayalım www.yourwebsite.com/search/thingsearchedfor. Arama sayfası bir grup arama sonucu içerir. Geri gelmek istemeleri ihtimaline karşı bu rotayı saklamak istiyorsunuz! Şimdi bir arama sonucunu tıklayın ve navigasyon olsun view/:resultIdhangi, yok onlar muhtemelen sadece bir kez orada olacağım gibi görerek, mağazaya istiyorum. Yukarıdaki uygulamayla, basitçe shouldDetachyöntemi değiştirirdim ! Şöyle görünebilir:

Öncelikle saklamak istediğimiz bir dizi yol yapalım.

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

şimdi, dizimize karşı shouldDetachkontrol edebiliriz 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 bir rotanın yalnızca bir örneğini depolayacağından , bu depolama hafif olacaktır ve yalnızca konumunda bulunan bileşeni depolayacağız search/:term, diğerlerini değil!

Ek Bağlantılar

Henüz çok fazla belge olmasa da, burada var olanlara birkaç bağlantı var:

Angular Docs: https://angular.io/docs/ts/latest/api/router/index/RouteReuseStrategy-class.html

Giriş Makalesi: https://www.softwarearchitekt.at/post/2016/12/02/sticky-routes-in-angular-2-3-with-routereusestrategy.aspx

nativescript-angular'ın varsayılan RouteReuseStrategy Uygulaması : https://github.com/NativeScript/nativescript-angular/blob/cb4fd3a/nativescript-angular/router/ns-route-reuse-strategy.ts


2
@shaahin Bir örnek ekledim, mevcut uygulamamdaki kodun aynısı!
Corbfon

1
@Corbfon Resmi github sayfasında da bir sayı açtım: github.com/angular/angular/issues/13869
Tjaart van der Walt

2
Depolanan bir rotayı yeniden etkinleştirirken animasyonları tekrar çalıştırmanın bir yolu var mı?
Jinder Sidhu

2
ReuseRouteStrategy, bileşeninizi yönlendiriciye geri verecektir, böylece bileşen içinde kaldığı durumda olacaktır. Bileşen (ler) in eke tepki vermesini istiyorsanız, bir Observable. Bileşen Observable, ngOnInityaşam döngüsü boyunca kancaya abone olmalıdır . Daha sonra, bileşenden ReuseRouteStrategyyeni eklendiğini ve bileşenin durumunu uygun olarak değiştirebileceğini söyleyebileceksiniz.
Corbfon

1
@AndersGramMygind eğer cevabım önerdiğiniz soruya bir cevap veriyorsa, cevap olarak işaretler misiniz?
Corbfon

45

Kabul edilen cevaptan korkmayın, bu oldukça basittir. İşte ihtiyacınız olan şeylere hızlı bir cevap. En azından kabul edilen cevabı okumanızı tavsiye ederim, çünkü çok detaylı.

Bu çözüm, kabul edilen cevap gibi herhangi bir parametre karşılaştırması yapmaz, ancak bir dizi rotayı depolamak için iyi çalışacaktır.

app.module.ts içe aktarır:

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

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

Paylaşılan / 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
Bu, tembel yüklü rotalar için de işe yarar mı?
bluePearl

@bluePearl Aşağıdaki yanıtı kontrol edin
Chris Fremgen

2
routeConfig null, farklı rotalar için, bu nedenle shouldReuseRoute her zaman istenen davranış değildir ki doğrudur dönecektir olduğunu
Gil Epshtain

19

Kabul edilen cevaba (Corbfon tarafından) ve Chris Fremgen'in daha kısa ve daha anlaşılır açıklamasına ek olarak, yeniden kullanım stratejisini kullanması gereken yolları idare etmenin daha esnek bir yolunu eklemek istiyorum.

Her iki yanıt da bir dizide önbelleğe almak istediğimiz yolları saklar ve ardından geçerli yol yolunun dizide olup olmadığını kontrol eder. Bu kontrol shouldDetachyöntemde yapılır .

Bu yaklaşımı esnek bulmuyorum çünkü rotanın adını değiştirmek istiyorsak, CustomReuseStrategysınıfımızdaki rota adını da değiştirmemiz gerektiğini hatırlamamız gerekecek . Ya değiştirmeyi unutabiliriz ya da ekibimizdeki başka bir geliştirici, rotanın varlığından haberdar bile olmadan rota adını değiştirmeye karar verebilir RouteReuseStrategy.

Önbelleğe almak istediğimiz rotaları bir dizide saklamak yerine, bunları doğrudan nesne RouterModulekullanarak işaretleyebiliriz data. Bu şekilde, rota adını değiştirsek bile, yeniden kullanım stratejisi yine de uygulanacaktır.

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

Ve sonra shouldDetachyöntemde bunu kullanırız.

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

1
Güzel çözüm. Bu gerçekten uyguladığınız gibi basit bir bayrakla standart açısal rotayı yeniden kullanma stratejisine dahil edilmelidir.
MIP1983

Mükemmel cevap. Çok teşekkür ederim!
claudiomatiasrg

14

Chris Fremgen'in stratejisini tembel yüklenen modüllerle kullanmak için, CustomReuseStrategy sınıfını aşağıdaki gibi değiştirin:

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

son olarak, özellik modüllerinizin yönlendirme dosyalarında anahtarlarınızı tanımlayın:

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

Daha fazla bilgi burada .


1
Bunu eklediğiniz için teşekkürler! Denemeliyim. Çözümümün karşılaştığı bazı alt yol işleme sorunlarını bile çözebilir.
Corbfon

route.data["key"]Hatasız inşa etmek için kullanmak zorunda kaldım . Ancak yaşadığım sorun, iki farklı yerde kullanılan bir rotam + bileşenimin olması. 1. sample/list/itemve 2. product/id/sample/list/itemyollardan herhangi birini ilk yüklediğimde iyi yüklüyor, ancak diğeri yeniden eklenmiş hatayı atıyor çünkü temelde depoluyorum list/itemBu yüzden çalışmam, rotayı kopyaladım ve url yolunda bazı değişiklikler yaptım ancak aynı bileşeni görüntülüyor. Bunun için başka bir çalışma olup olmadığından emin değilim.
bluePearl

Bu tür kafamı karıştırdı, yukarıdakiler işe yaramazdı, önbellek rotalarımdan birine ulaşır ulaşmaz patlardı (artık gezinmez ve konsolda hataların olduğu yerde). Chris Fremgen'in çözümü şu ana kadar söyleyebildiğim kadarıyla tembel modüllerimle iyi çalışıyor gibi görünüyor ...
MIP1983

12

Başka bir uygulama daha geçerli, eksiksiz ve yeniden kullanılabilir. Bu, @ Uğur Dinç gibi tembel yüklenen modülleri destekler ve @Davor rota veri işaretini entegre eder. En iyi gelişme, sayfanın mutlak yolunu temel alan (neredeyse) benzersiz bir tanımlayıcının otomatik olarak oluşturulmasıdır. Bu şekilde her sayfada kendiniz tanımlamanız gerekmez.

Ayarı önbelleğe almak istediğiniz herhangi bir sayfayı işaretleyin reuseRoute: true. shouldDetachYöntemde kullanılacaktır .

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

Bu, sorgu parametrelerini karşılaştırmadan en basit strateji uygulamasıdır.

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

Bu aynı zamanda sorgu parametrelerini de karşılaştırır. compareObjects@Corbfon sürümüne göre küçük bir gelişmeye sahiptir: hem temel hem de nesneleri karşılaştırma özelliklerinde döngü. Lodash isEqualyöntemi gibi harici ve daha güvenilir bir uygulama kullanabileceğinizi unutmayın .

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

Benzersiz anahtarlar oluşturmanın en iyi yolu varsa cevabıma yorum yapın, kodu güncelleyeceğim.

Çözümlerini paylaşan tüm adamlara teşekkür ederim.


3
Kabul edilen cevap bu olmalıdır. Yukarıda sağlanan birçok çözüm, aynı alt URL'ye sahip birden çok sayfayı destekleyemez. Çünkü tam yol olmayan activedRoute URL'sini karşılaştırıyorlar.
zhuhang.jasper

4

Bizim durumumuzda bahsedilen tüm çözümler bir şekilde yetersizdi. Daha küçük işletme uygulamamız var:

  1. Giriş sayfası
  2. Giriş sayfası
  3. Uygulama (giriş yaptıktan sonra)

Gereksinimlerimiz:

  1. Tembel yüklü modüller
  2. Çok seviyeli rotalar
  3. Tüm yönlendirici / bileşen durumlarını uygulama bölümünde bellekte saklayın
  4. Belirli rotalarda varsayılan açısal yeniden kullanım stratejisini kullanma seçeneği
  5. Oturumu kapattığınızda bellekte depolanan tüm bileşenleri yok etme

Rotalarımızın basitleştirilmiş örneği:

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

Yeniden kullanım stratejisi:

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

aşağıdaki iş! referans: 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
Dikkatli olun, bu dahili bir değişken kullanır _routerState.
DarkNeuron

@DarkNeuron _routerStateherhangi bir zararlıya neden olur mu?
k11k2

2
Hayır, ancak dahili olarak kullanıldığı ve API'de gösterilmediği için Google'ın bu değişkeni etrafta tutma yükümlülüğü yoktur.
DarkNeuron

ne zaman arıyoruz deleteRouteSnapshot?
k11k2

0

Özel bir rotayı yeniden kullanma stratejisi uygularken şu sorunlarla karşılaştım:

  1. Bir rota ekleme / çıkarma üzerinde işlemler gerçekleştirme: abonelikleri yönetme, temizleme, vb .;
  2. Yalnızca son parametreli yol durumunu koruyun: bellek optimizasyonu;
  3. Bir durumu değil, bir bileşeni yeniden kullanın: durumu bir devlet yönetim araçlarıyla yönetin.
  4. "Farklı bir rotadan oluşturulan ActivatedRouteSnapshot yeniden eklenemiyor" hatası;

Ben de bu sorunları çözen bir kütüphane yazdım. Kitaplık, kancaları eklemek / ayırmak için bir hizmet ve dekoratörler sağlar ve bir yolun yollarını değil, ayrılmış yolları depolamak için bir yolun bileşenlerini kullanır.

Misal:

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

Kitaplık: https://www.npmjs.com/package/ng-cache-route-reuse


Kendi kitaplığınıza veya eğiticinize bağlantı vermek iyi bir cevap değildir. Buna bağlanmak, sorunu neden çözdüğünü açıklamak, nasıl yapılacağına dair kod sağlamak ve yazdığınızı reddetmek daha iyi bir yanıt sağlar. Bakınız: Kendini tanıtmanın “iyi” anlamına gelen nedir?
Paul Roub
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.