Cách triển khai RouteReuseStrategy shouldDetach cho các tuyến đường cụ thể trong Angular 2


115

Tôi có một mô-đun Angular 2 trong đó tôi đã triển khai định tuyến và muốn các trạng thái được lưu trữ khi điều hướng.
Người dùng sẽ có thể:

  1. tìm kiếm tài liệu bằng 'công thức tìm kiếm'
  2. điều hướng đến một trong các kết quả
  3. điều hướng trở lại 'kết quả tìm kiếm' - mà không cần giao tiếp với máy chủ

Điều này có thể bao gồm RouteReuseStrategy.
Câu hỏi đặt ra là:
Làm cách nào để triển khai tài liệu không nên lưu trữ?

Vì vậy, trạng thái của đường dẫn tuyến đường là "tài liệu" nên được lưu trữ và trạng thái của đường dẫn tuyến đường "tài liệu /: id" 'KHÔNG nên được lưu trữ?

Câu trả lời:


209

Này Anders, câu hỏi hay!

Tôi gần như có cùng một trường hợp sử dụng như bạn và muốn làm điều tương tự! Người dùng tìm kiếm> lấy kết quả> Người dùng điều hướng đến kết quả> Người dùng điều hướng trở lại> BOOM trả về kết quả nhanh chóng , nhưng bạn không muốn lưu trữ kết quả cụ thể mà người dùng đã điều hướng đến.

tl; dr

Bạn cần phải có một lớp thực hiện RouteReuseStrategyvà cung cấp chiến lược của bạn trong ngModule. Nếu bạn muốn sửa đổi khi tuyến đường được lưu trữ, hãy sửa đổi shouldDetachchức năng. Khi nó quay trở lại true, Angular lưu trữ tuyến đường. Nếu bạn muốn sửa đổi khi tuyến đường được đính kèm, hãy sửa đổi shouldAttachchức năng. Khi shouldAttachtrả về true, Angular sẽ sử dụng tuyến đã lưu thay cho tuyến được yêu cầu. Đây là một Plunker để bạn chơi cùng.

Giới thiệu về RouteReuseStrategy

Khi đặt câu hỏi này, bạn đã hiểu rằng RouteReuseStrategy cho phép bạn yêu cầu Angular không phá hủy một thành phần, nhưng trên thực tế là để lưu nó để hiển thị lại sau này. Điều đó thật tuyệt vì nó cho phép:

  • Giảm cuộc gọi máy chủ
  • Tăng tốc độ
  • thành phần hiển thị, theo mặc định, ở cùng trạng thái mà nó được để lại

Điều cuối cùng rất quan trọng nếu bạn muốn tạm thời rời khỏi một trang ngay cả khi người dùng đã nhập rất nhiều văn bản vào đó. Các ứng dụng doanh nghiệp sẽ thích tính năng này vì số lượng biểu mẫu quá nhiều !

Đây là những gì tôi nghĩ ra để giải quyết vấn đề. Như bạn đã nói, bạn cần tận dụng các tính năng RouteReuseStrategyđược cung cấp bởi @ angle / router trong các phiên bản 3.4.1 trở lên.

LÀM

Đầu tiên Đảm bảo rằng dự án của bạn có @ angle / router phiên bản 3.4.1 trở lên.

Tiếp theo , tạo một tệp sẽ chứa lớp triển khai của bạn RouteReuseStrategy. Tôi đã gọi của tôi reuse-strategy.tsvà đặt nó vào /appthư mục để lưu giữ an toàn. Hiện tại, lớp này sẽ giống như sau:

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

export class CustomReuseStrategy implements RouteReuseStrategy {
}

(đừng lo lắng về lỗi TypeScript của bạn, chúng tôi sắp giải quyết mọi thứ)

Hoàn thành công việc cơ bản bằng cách cung cấp lớp học cho bạn app.module. Lưu ý rằng bạn vẫn chưa viết CustomReuseStrategy, nhưng hãy tiếp tục và importnó từ reuse-strategy.tstất cả những điều tương tự. Cũng thếimport { RouteReuseStrategy } from '@angular/router';

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

Phần cuối cùng là viết lớp sẽ kiểm soát việc các tuyến có được tách ra, lưu trữ, truy xuất và gắn lại hay không. Trước khi chúng ta chuyển sang bản sao / dán cũ , tôi sẽ giải thích ngắn gọn về cơ học ở đây, vì tôi hiểu chúng. Tham khảo mã bên dưới để biết các phương pháp tôi đang mô tả và tất nhiên, có rất nhiều tài liệu trong mã .

  1. Khi bạn điều hướng, shouldReuseRoutekích hoạt. Điều này hơi kỳ lạ đối với tôi, nhưng nếu nó trả về true, thì nó thực sự sử dụng lại tuyến đường bạn đang truy cập và không có phương thức nào khác được kích hoạt. Tôi chỉ trả về false nếu người dùng đang điều hướng đi.
  2. Nếu shouldReuseRoutetrả lại false, shouldDetachcháy. shouldDetachxác định xem bạn có muốn lưu trữ tuyến đường hay không và trả về một booleanchỉ báo càng nhiều. Đây là nơi bạn nên quyết định lưu trữ / không lưu trữ đường dẫn , điều này tôi sẽ làm bằng cách kiểm tra một mảng đường dẫn bạn muốn được lưu trữ route.routeConfig.pathvà trả về false nếu pathkhông tồn tại trong mảng.
  3. Nếu bị shouldDetachtrả lại true, storebị sa thải, đó là cơ hội để bạn lưu trữ bất kỳ thông tin nào bạn muốn về lộ trình. Dù bạn làm gì, bạn sẽ cần phải lưu trữ DetachedRouteHandlevì đó là những gì Angular sử dụng để xác định thành phần được lưu trữ của bạn sau này. Dưới đây, tôi lưu trữ cả the DetachedRouteHandlevà the ActivatedRouteSnapshotvào một biến cục bộ cho lớp của tôi.

Vì vậy, chúng ta đã thấy logic để lưu trữ, nhưng điều hướng đến một thành phần thì sao? Làm thế nào để Angular quyết định chặn điều hướng của bạn và đặt điều hướng đã lưu vào vị trí của nó?

  1. Một lần nữa, sau khi shouldReuseRouteđã quay trở lại false, hãy shouldAttachchạy, đây là cơ hội để bạn tìm hiểu xem bạn muốn tạo lại hoặc sử dụng thành phần trong bộ nhớ. Nếu bạn muốn sử dụng lại một thành phần đã lưu trữ, hãy quay lại truevà bạn đang trên đường đi của mình!
  2. Bây giờ kiễu góc sẽ yêu cầu bạn "mà thành phần nào bạn muốn chúng tôi sử dụng không?", Mà bạn sẽ chỉ ra bằng cách quay đó của thành phần DetachedRouteHandletừ retrieve.

Đó là khá nhiều logic bạn cần! Trong đoạn mã reuse-strategy.tsdưới đây, tôi cũng đã để lại cho bạn một hàm tiện lợi sẽ so sánh hai đối tượng. Tôi sử dụng nó để so sánh lộ trình trong tương lai route.paramsroute.queryParamsvới lộ trình được lưu trữ. Nếu tất cả những thứ đó đều khớp, tôi muốn sử dụng thành phần được lưu trữ thay vì tạo một thành phần mới. Nhưng làm thế nào bạn làm điều đó là tùy thuộc vào bạn!

tái sử dụng-chiến lược.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;
    }
}

Hành vi

Việc triển khai này lưu trữ mọi tuyến đường duy nhất mà người dùng truy cập trên bộ định tuyến chính xác một lần. Điều này sẽ tiếp tục thêm vào các thành phần được lưu trữ trong bộ nhớ trong suốt phiên của người dùng trên trang web. Nếu bạn muốn giới hạn các tuyến đường mà bạn lưu trữ, thì nơi để làm điều đó là shouldDetachphương pháp. Nó kiểm soát những tuyến đường bạn lưu.

Thí dụ

Giả sử người dùng của bạn tìm kiếm thứ gì đó từ trang chủ, điều hướng họ đến đường dẫn search/:term, có thể xuất hiện như sau www.yourwebsite.com/search/thingsearchedfor. Trang tìm kiếm chứa nhiều kết quả tìm kiếm. Bạn muốn lưu trữ tuyến đường này, trong trường hợp họ muốn quay lại! Bây giờ họ nhấp vào một kết quả tìm kiếm và được điều hướng đến kết quả view/:resultIdmà bạn không muốn lưu trữ, vì họ có thể sẽ chỉ ở đó một lần. Với việc thực hiện ở trên, tôi chỉ cần thay đổi shouldDetachphương pháp! Đây là những gì nó có thể trông như thế này:

Trước hết, hãy tạo một mảng đường dẫn mà chúng ta muốn lưu trữ.

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

bây giờ, trong shouldDetachchúng ta có thể kiểm tra route.routeConfig.pathmảng của chúng ta.

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

Bởi vì Angular sẽ chỉ lưu trữ một phiên bản của một tuyến đường, bộ lưu trữ này sẽ nhẹ và chúng tôi sẽ chỉ lưu trữ thành phần được đặt tại search/:termchứ không phải tất cả các thành phần khác!

Liên kết bổ sung

Mặc dù chưa có nhiều tài liệu, đây là một vài liên kết đến những gì tồn tại:

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

Bài viết giới thiệu: https://www.softwarearchitekt.at/post/2016/12/02/sticky-routes-in-angular-2-3-with-routereusestrategy.aspx

Triển khai mặc định của nativescript-angle của RouteReuseStrategy : https://github.com/NativeScript/nativescript-angular/blob/cb4fd3a/nativescript-angular/router/ns-route-reuse-strategy.ts


2
@shaahin Tôi đã thêm một ví dụ, đó là mã chính xác có trong triển khai hiện tại của tôi!
Corbfon

1
@Corbfon Tôi cũng đã mở một vấn đề trên trang github chính thức: github.com/angular/angular/issues/13869
Tjaart van der Walt

2
Có cách nào để làm cho nó chạy lại nhập hoạt ảnh khi kích hoạt lại một tuyến đường được lưu trữ không?
Jinder Sidhu

2
ReuseRouteStrategy sẽ đưa thành phần của bạn trở lại bộ định tuyến, vì vậy nó sẽ ở trạng thái bất kỳ mà nó được giữ nguyên. Nếu bạn muốn (các) thành phần phản ứng với tệp đính kèm, bạn có thể sử dụng dịch vụ cung cấp Observable. Thành phần phải đăng ký vào hook Observabletrong ngOnInitvòng đời. Sau đó, bạn sẽ có thể nói với thành phần, từ ReuseRouteStrategy, rằng nó vừa được đính kèm và thành phần có thể sửa đổi trạng thái của nó cho phù hợp.
Corbfon

1
@AndersGramMygind nếu câu trả lời của tôi cung cấp câu trả lời cho câu hỏi bạn đề xuất, bạn có đánh dấu nó là câu trả lời không?
Corbfon

45

Đừng sợ hãi bởi câu trả lời được chấp nhận, điều này khá đơn giản. Đây là câu trả lời nhanh những gì bạn cần. Tôi khuyên bạn nên ít nhất đọc câu trả lời được chấp nhận, vì nó đầy đủ chi tiết.

Giải pháp này không thực hiện bất kỳ so sánh tham số nào như câu trả lời được chấp nhận nhưng nó sẽ hoạt động tốt khi lưu trữ một tập hợp các tuyến đường.

Nhập 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
Điều này cũng sẽ hoạt động đối với các tuyến đường được tải chậm?
bluePearl

@bluePearl Kiểm tra câu trả lời dưới đây
Chris Fremgen

2
routeConfig là null, cho các tuyến đường khác nhau, do đó shouldReuseRoute sẽ luôn luôn trở thành sự thật mà không phải là hành vi mong muốn
Gil Epshtain

19

Ngoài câu trả lời được chấp nhận (của Corbfon) và lời giải thích ngắn gọn và dễ hiểu hơn của Chris Fremgen, tôi muốn thêm một cách xử lý các tuyến linh hoạt hơn nên sử dụng chiến lược tái sử dụng.

Cả hai câu trả lời đều lưu trữ các tuyến đường mà chúng ta muốn lưu vào bộ nhớ cache trong một mảng và sau đó kiểm tra xem đường dẫn tuyến hiện tại có nằm trong mảng hay không. Kiểm tra này được thực hiện trongshouldDetach phương pháp.

Tôi thấy cách tiếp cận này không linh hoạt vì nếu chúng ta muốn thay đổi tên của tuyến đường, chúng ta sẽ cần nhớ cũng thay đổi tên tuyến đường trong CustomReuseStrategylớp của chúng ta . Chúng tôi có thể quên thay đổi nó hoặc một số nhà phát triển khác trong nhóm của chúng tôi có thể quyết định thay đổi tên tuyến đường ngay cả khi không biết về sự tồn tại của RouteReuseStrategy.

Thay vì lưu trữ các tuyến đường mà chúng ta muốn lưu vào bộ nhớ cache trong một mảng, chúng ta có thể đánh dấu chúng trực tiếp RouterModulebằng cách sử dụng datađối tượng. Bằng cách này, ngay cả khi chúng tôi thay đổi tên tuyến đường, chiến lược tái sử dụng vẫn sẽ được áp dụng.

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

Và sau đó trong shouldDetachphương pháp, chúng tôi sử dụng nó.

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

1
Giải pháp tốt. Điều này thực sự chỉ nên được đưa vào chiến lược tái sử dụng tuyến đường góc tiêu chuẩn với một cờ đơn giản như bạn đã áp dụng.
MIP1983

Câu trả lời chính xác. Cảm ơn rât nhiều!
claudiomatiasrg

14

Để sử dụng chiến lược của Chris Fremgen với các mô-đun được tải chậm, hãy sửa đổi lớp CustomReuseStrategy thành như sau:

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

cuối cùng, trong các tệp định tuyến của mô-đun tính năng của bạn, hãy xác định các khóa của bạn:

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

Thêm thông tin ở đây .


1
Cảm ơn vì đã thêm điều này! Tôi phải thử. Nó thậm chí có thể khắc phục một số vấn đề xử lý tuyến đường con mà giải pháp của tôi gặp phải.
Corbfon

Tôi đã phải sử dụng route.data["key"]để xây dựng mà không có lỗi. Nhưng vấn đề tôi đang gặp phải là tôi có một tuyến đường + thành phần được sử dụng ở hai nơi khác nhau. 1. sample/list/item2. product/id/sample/list/itemkhi tôi lần đầu tiên tải một trong hai đường dẫn thì nó tải tốt nhưng đường còn lại ném ra lỗi được đính kèm lại bởi vì tôi đang lưu trữ dựa trên list/itemVì vậy, công việc của tôi xung quanh là tôi sao chép tuyến đường và thực hiện một số thay đổi đối với đường dẫn url nhưng hiển thị cùng một thành phần. Không chắc liệu có một công việc khác xung quanh điều đó.
bluePearl

Loại này khiến tôi bối rối, điều trên sẽ không hoạt động, nó sẽ nổ tung ngay khi tôi chạm vào một trong các tuyến bộ nhớ cache của mình, (nó sẽ không điều hướng nữa và ở đó có lỗi trong bảng điều khiển). Giải pháp Chris Fremgen dường như làm việc tốt với các module lười biếng của tôi như xa như tôi có thể nói cho đến nay ...
MIP1983

12

Một triển khai khác hợp lệ hơn, đầy đủ và có thể sử dụng lại. Cái này hỗ trợ các mô-đun được tải lười biếng là @ Uğur Dinç và tích hợp cờ dữ liệu tuyến đường @Davor. Cải tiến tốt nhất là tự động tạo (gần như) giá trị nhận dạng duy nhất dựa trên đường dẫn tuyệt đối của trang. Bằng cách này, bạn không phải tự mình xác định nó trên mỗi trang.

Đánh dấu trang bất kỳ mà bạn muốn cài đặt bộ nhớ cache reuseRoute: true. Nó sẽ được sử dụng trong shouldDetachphương thức.

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

Đây là cách triển khai chiến lược đơn giản nhất mà không cần so sánh các tham số truy vấn.

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

Cái này cũng so sánh các tham số truy vấn. compareObjectscó một chút cải tiến so với phiên bản @Corbfon: lặp qua các thuộc tính của cả đối tượng cơ sở và so sánh. Hãy nhớ rằng bạn có thể sử dụng một triển khai bên ngoài và đáng tin cậy hơn như isEqualphương pháp 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;
  }
}

Nếu bạn có cách tốt nhất để tạo khóa duy nhất, hãy bình luận câu trả lời của tôi, tôi sẽ cập nhật mã.

Cảm ơn tất cả những người đã chia sẻ giải pháp của họ.


3
Đây phải là câu trả lời được chấp nhận. Nhiều giải pháp được cung cấp ở trên không thể hỗ trợ nhiều trang có cùng một URL con. Bởi vì họ đang so sánh URL enableRoute, không phải là đường dẫn đầy đủ.
zhuhang.jasper

4

Tất cả các giải pháp được đề cập bằng cách nào đó không đủ trong trường hợp của chúng tôi. Chúng tôi có ứng dụng kinh doanh nhỏ hơn với:

  1. Trang giới thiệu
  2. Trang đăng nhập
  3. Ứng dụng (sau khi đăng nhập)

Yêu cầu của chúng tôi:

  1. Mô-đun được tải chậm
  2. Các tuyến đường đa cấp
  3. Lưu trữ tất cả các trạng thái của bộ định tuyến / thành phần trong bộ nhớ trong phần ứng dụng
  4. Tùy chọn sử dụng chiến lược tái sử dụng góc mặc định trên các tuyến đường cụ thể
  5. Phá hủy tất cả các thành phần được lưu trữ trong bộ nhớ khi đăng xuất

Ví dụ đơn giản về các tuyến đường của chúng tô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
    }
}];

Chiến lược tái sử dụng:

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

sau đây là công việc! tham khảo: 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
Hãy cẩn thận, điều này sử dụng một biến nội bộ _routerState.
DarkNeuron

@DarkNeuron có _routerStategây hại gì không?
k11k2

2
Không, nhưng Google không có nghĩa vụ phải giữ biến đó xung quanh, vì nó được sử dụng nội bộ và không được hiển thị trong API.
DarkNeuron 21/12/18

khi chúng tôi đang gọi deleteRouteSnapshot?
k11k2

0

Tôi đã gặp phải những vấn đề này khi triển khai chiến lược tái sử dụng tuyến đường tùy chỉnh:

  1. Thực hiện các hoạt động trên một phần đính kèm / tách rời tuyến đường: quản lý đăng ký, dọn dẹp, v.v.;
  2. Chỉ bảo tồn trạng thái của tuyến đường được tham số hóa cuối cùng: tối ưu hóa bộ nhớ;
  3. Sử dụng lại một thành phần, không phải trạng thái: quản lý trạng thái bằng công cụ quản lý trạng thái.
  4. Lỗi "Không thể đính kèm lại ActiisedRouteSnapshot được tạo từ một tuyến đường khác";

Vì vậy, tôi đã viết một thư viện giải quyết những vấn đề này. Thư viện cung cấp một dịch vụ và trình trang trí cho các móc đính kèm / tách rời và sử dụng các thành phần của một tuyến để lưu trữ các tuyến tách rời, không phải các đường dẫn của một tuyến.

Thí dụ:

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

Thư viện: https://www.npmjs.com/package/ng-cache-route-reuse


Chỉ liên kết đến thư viện hoặc hướng dẫn của riêng bạn không phải là một câu trả lời hay. Liên kết với nó, giải thích tại sao nó giải quyết được vấn đề, cung cấp mã về cách làm như vậy và từ chối rằng bạn đã viết nó sẽ tạo ra câu trả lời tốt hơn. Hãy xem: Điều gì biểu hiện sự tự quảng cáo “Tốt”?
Paul Roub
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.