Angular 2의 특정 경로에 대해 RouteReuseStrategy shouldDetach를 구현하는 방법


114

라우팅을 구현 한 Angular 2 모듈이 있으며 탐색 할 때 상태를 저장하고 싶습니다. 사용자는 다음을 수행 할 수 있어야합니다. 1. 검색 공식을 사용하여 문서 검색 2. 결과 중 하나로 이동 3. 검색 결과로 다시 이동-서버와 통신하지 않고

이는 RouteReuseStrategy를 포함하여 가능합니다. 질문은 문서가 저장되지 않도록 구현하는 방법입니다.

그래서 경로 경로 "documents"의 상태를 저장해야하고 경로 경로 "documents / : id" '상태를 저장하지 않아야합니까?

답변:


209

안녕하세요 Anders, 좋은 질문입니다!

나는 당신과 거의 같은 사용 사례를 가지고 있으며 같은 일을하고 싶었습니다! 사용자 검색> 결과 가져 오기> 사용자가 결과로 이동> 사용자가 뒤로 이동> BOOM 결과로 빠르게 돌아가 지만 사용자가 탐색 한 특정 결과를 저장하고 싶지 않습니다.

tl; dr

RouteReuseStrategy.NET Framework에서 전략 을 구현 하고 제공 하는 클래스가 있어야 합니다 ngModule. 경로가 저장 될 때 수정하려면 shouldDetach기능을 수정하십시오 . 반환 true되면 Angular는 경로를 저장합니다. 루트가 부착 된 시점을 수정하려면 shouldAttach기능을 수정하십시오 . shouldAttachtrue를 반환 하면 Angular는 요청 된 경로 대신 저장된 경로를 사용합니다. 여기 에 플레이 할 수 있는 Plunker 가 있습니다.

RouteReuseStrategy 정보

이 질문을 통해 RouteReuseStrategy를 사용하면 Angular 가 구성 요소를 파괴 하지 않고 실제로 나중에 다시 렌더링하기 위해 저장 하도록 지시 할 수 있음을 이미 이해했습니다 . 다음을 허용하기 때문에 멋집니다.

  • 서버 호출 감소
  • 증가 된 속도
  • 그리고 구성 요소는 기본적으로 남은 상태로 렌더링됩니다.

마지막 페이지는 사용자가 페이지에 많은 텍스트를 입력 했음에도 불구하고 일시적으로 페이지를 떠나려는 경우에 중요 합니다. 과도한 양의 양식 때문에 엔터프라이즈 응용 프로그램은이 기능을 좋아할 것입니다 !

이것이 제가 문제를 해결하기 위해 생각 해낸 것입니다. 말했듯이 RouteReuseStrategy버전 3.4.1 이상에서 @ angular / router가 제공하는을 사용해야합니다 .

할 것

먼저 프로젝트에 @ angular / router 버전 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하면 실제로 현재 사용중인 경로를 재사용하고 다른 메소드는 실행되지 않습니다. 사용자가 멀리 이동하면 false를 반환합니다.
  2. 만약 shouldReuseRoute반환 false, shouldDetach화재. shouldDetach경로를 저장할 것인지 여부를 결정하고 boolean표시하는만큼 반환합니다 . 당신은 저장하지 경로에 / 저장에 결정해야하는 곳이다 당신이 경로의 배열을 확인하여 할 것, 원하는 에 저장을 route.routeConfig.path하고,이 경우는 false를 반환 path배열에 존재하지 않습니다.
  3. 경우 shouldDetach반환 true, store당신은 당신이 경로에 대해 원하는 정보를 모두 저장할 수있는 기회 인, 발생합니다. 무엇을 하든지간에 DetachedRouteHandleAngular가 나중에 저장된 구성 요소를 식별하는 데 사용 하는 것이기 때문에 를 저장해야합니다 . 다음, 나는 모두 저장 DetachedRouteHandle하고 ActivatedRouteSnapshot내 클래스에 변수 지역에 있습니다.

그래서 우리는 저장 로직을 보았지만 컴포넌트로 이동 하는 것은 어떨까요? Angular는 내비게이션을 가로 채서 저장된 내비게이션을 제자리에두기로 결정합니까?

  1. 다시, 후 shouldReuseRoute돌아왔다 false, shouldAttach당신은 재생 또는 메모리에있는 구성 요소를 사용할지 여부를 알아낼 수있는 기회 인 실행합니다. 저장된 구성 요소를 재사용 true하려면 돌아 가면됩니다 .
  2. 해당 구성 요소의 반환에 의해 표시되는, "당신은 우리가 사용하려는 않는 구성 요소?"를 묻습니다 이제 각도 DetachedRouteHandle에서 retrieve.

그것은 당신이 필요로하는 거의 모든 논리입니다! 아래의에 대한 코드에서 reuse-strategy.ts두 개체를 비교할 수있는 멋진 함수도 남겼습니다. 나는 미래 경로의 비교에 사용 route.params하고 route.queryParams저장된 하나 개의과. 모두 일치하면 새 구성 요소를 생성하는 대신 저장된 구성 요소를 사용하고 싶습니다. 그러나 어떻게하는지는 당신에게 달려 있습니다!

재사용 전략 .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"];

이제 배열 shouldDetachroute.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다른 모든 인스턴스 가 아닌에있는 구성 요소 만 저장합니다 !

추가 링크

아직 문서가 많지는 않지만 여기에 몇 가지 링크가 있습니다.

Angular 문서 : 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

의 nativescript - 각도의 기본 구현 RouteReuseStrategy : https://github.com/NativeScript/nativescript-angular/blob/cb4fd3a/nativescript-angular/router/ns-route-reuse-strategy.ts


2
@shaahin 현재 구현에 포함 된 정확한 코드 인 예제를 추가했습니다!
Corbfon

1
또한 공식 GitHub의 페이지에서 문제를 열었습니다 @Corbfon : github.com/angular/angular/issues/13869
Tjaart 밴 월트 데르

2
저장된 경로를 다시 활성화 할 때 엔터 애니메이션을 다시 실행하는 방법이 있습니까?
Jinder Sidhu

2
ReuseRouteStrategy는 구성 요소를 라우터에 다시 전달하므로 남은 상태에 관계없이 구성 요소 가 첨부 파일 에 반응 하도록하려면 Observable. 구성 요소는 수명주기 Observable동안 ngOnInit훅을 구독해야합니다 . 그런 다음에서 구성 요소 ReuseRouteStrategy가 방금 연결되었으며 구성 요소가 상태를 적절하게 수정할 수 있음을 알 수 있습니다.
Corbfon

1
@AndersGramMygind 내 답변이 귀하가 제안한 질문에 대한 답변을 제공하는 경우 답변으로 표시 하시겠습니까?
Corbfon

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
크리스 Fremgen

2
routeConfig가 null는, 다른 경로에 대한 따라서 shouldReuseRoute 항상 원하는 동작하지 않은 true를 돌려 것이다
길 Epshtain

19

받아 들여지는 답변 (Corbfon)과 Chris Fremgen의 짧고 간단한 설명 외에도 재사용 전략을 사용해야하는 경로를 처리하는 더 유연한 방법을 추가하고 싶습니다.

두 답변 모두 배열에 캐시하려는 경로를 저장 한 다음 현재 경로 경로가 배열에 있는지 확인합니다. 이 검사는 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

지연로드 된 모듈에서 Chris Fremgen의 전략을 사용하려면 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
추가해 주셔서 감사합니다! 나는 그것을 시도해야한다. 내 솔루션이 실행되는 하위 경로 처리 문제 중 일부를 수정할 수도 있습니다.
Corbfon

route.data["key"]오류없이 빌드 하기 위해 사용해야 했습니다. 그러나 내가 가진 문제는 두 곳에서 사용되는 경로 + 구성 요소가 있다는 것입니다. 1. sample/list/item그리고 2. product/id/sample/list/item내가 경로 중 하나를 처음로드하면 잘로드되지만 다른 하나는 다시 첨부 된 오류를 던집니다. 왜냐하면 내가 기반으로 저장하고 있기 때문에 list/item내 작업은 경로를 복제하고 URL 경로를 약간 변경했지만 동일한 구성 요소를 표시하는 것입니다. 그것에 대한 다른 해결 방법이 있는지 확실하지 않습니다.
bluePearl dec.

이런 종류의 혼란스러워서 위의 내용은 작동하지 않고 캐시 경로 중 하나에 도달하자마자 폭발합니다 (더 이상 탐색하지 않고 콘솔의 오류가 있음). 크리스 Fremgen의 솔루션은 내가 ... 지금까지 말할 수있는 지금까지 나의 게으른 모듈 잘 작동 보인다
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 버전에 비해 약간 개선되었습니다. 기본 및 비교 개체의 속성을 통해 반복합니다. lodash isEqual메서드 와 같은 외부적이고 더 안정적인 구현을 사용할 수 있습니다 .

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을 가진 여러 페이지를 지원할 수 없습니다. 전체 경로가 아닌 activateRoute URL을 비교하고 있기 때문입니다.
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. "다른 경로에서 생성 된 ActivatedRouteSnapshot을 다시 연결할 수 없습니다."오류;

그래서 저는 이러한 문제를 해결하는 도서관을 썼습니다. 라이브러리는 연결 / 분리 후크에 대한 서비스 및 데코레이터를 제공하고 경로의 구성 요소를 사용하여 경로의 경로가 아닌 분리 된 경로를 저장합니다.

예:

/* 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


자신의 라이브러리 나 튜토리얼에 연결하는 것만으로는 좋은 대답이 아닙니다. 여기에 연결하고, 문제를 해결하는 이유를 설명하고,이를 수행하는 방법에 대한 코드를 제공하고, 작성한 사실을 부인하면 더 나은 답을 얻을 수 있습니다. 참조 : "좋은"자기 프로모션을 의미하는 것은 무엇입니까?
Paul Roub
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.