วิธีการใช้งาน RouteReuseStrategy shouldDetach สำหรับเส้นทางเฉพาะใน Angular 2


117

ฉันมีโมดูล Angular 2 ที่ฉันใช้งานการกำหนดเส้นทางและต้องการให้รัฐเก็บไว้เมื่อนำทาง
ผู้ใช้ควรจะสามารถ:

  1. ค้นหาเอกสารโดยใช้ 'สูตรการค้นหา'
  2. ไปที่ผลลัพธ์อย่างใดอย่างหนึ่ง
  3. กลับไปที่ 'searchresult' - โดยไม่ต้องสื่อสารกับเซิร์ฟเวอร์

RouteReuseStrategyนี้เป็นไปได้รวมถึง
คำถามคือ
ฉันจะนำเอกสารนั้นไปใช้งานได้อย่างไร?

ดังนั้นเส้นทางเส้นทาง "สถานะของเอกสาร" ควรถูกจัดเก็บและไม่ควรจัดเก็บสถานะเส้นทาง "เอกสาร /: id"?

คำตอบ:


211

เฮ้แอนเดอร์สคำถามที่ดี!

ฉันมีเคสการใช้งานเกือบเหมือนคุณและอยากจะทำแบบเดียวกัน! การค้นหาผู้ใช้> รับผลลัพธ์> ผู้ใช้นำทางไปยังผลลัพธ์> ผู้ใช้นำทางย้อนกลับ> BOOM กลับไปที่ผลลัพธ์อย่างรวดเร็วแต่คุณไม่ต้องการเก็บผลลัพธ์เฉพาะที่ผู้ใช้นำทางไป

tl; dr

คุณต้องมีคลาสที่ใช้RouteReuseStrategyและจัดเตรียมกลยุทธ์ของคุณในngModule. หากคุณต้องการแก้ไขเมื่อจัดเก็บเส้นทางให้ปรับเปลี่ยนshouldDetachฟังก์ชัน เมื่อกลับtrueมา Angular จะจัดเก็บเส้นทาง หากคุณต้องการแก้ไขเมื่อเชื่อมต่อเส้นทางให้ปรับเปลี่ยนshouldAttachฟังก์ชัน เมื่อshouldAttachคืนค่าเป็นจริง Angular จะใช้เส้นทางที่เก็บไว้แทนเส้นทางที่ร้องขอ นี่คือPlunkerสำหรับคุณที่จะเล่นด้วย

เกี่ยวกับ RouteReuseStrategy

เมื่อถามคำถามนี้แสดงว่าคุณเข้าใจแล้วว่า RouteReuseStrategy ช่วยให้คุณสามารถบอก Angular ไม่ให้ทำลายส่วนประกอบ แต่ในความเป็นจริงให้บันทึกไว้เพื่อแสดงผลในภายหลัง เจ๋งมากเพราะมันช่วยให้:

  • การเรียกเซิร์ฟเวอร์ลดลง
  • เพิ่มความเร็ว
  • และคอมโพเนนต์จะแสดงผลโดยค่าเริ่มต้นในสถานะเดียวกับที่ถูกทิ้งไว้

สิ่งสุดท้ายมีความสำคัญหากคุณต้องการพูดออกจากเพจชั่วคราวแม้ว่าผู้ใช้จะป้อนข้อความจำนวนมากลงไปก็ตาม แอปพลิเคชันระดับองค์กรจะชอบฟีเจอร์นี้เนื่องจากมีแบบฟอร์มจำนวนมากเกินไป !

นี่คือสิ่งที่ฉันคิดขึ้นมาเพื่อแก้ปัญหา อย่างที่คุณบอกคุณต้องใช้ประโยชน์RouteReuseStrategyจาก @ angular / router ที่เสนอในเวอร์ชัน 3.4.1 ขึ้นไป

ทำ

ขั้นแรกตรวจสอบให้แน่ใจว่าโปรเจ็กต์ของคุณมี @ 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มาแสดงว่ามันใช้เส้นทางที่คุณกำลังใช้งานอยู่และไม่มีวิธีอื่นใดที่จะถูกไล่ออก ฉันเพียงแค่ส่งคืนเท็จหากผู้ใช้กำลังออกไป
  2. หากshouldReuseRouteผลตอบแทนfalse, shouldDetachการเกิดเพลิงไหม้ shouldDetachกำหนดว่าคุณต้องการจัดเก็บเส้นทางหรือไม่และส่งกลับการbooleanระบุว่ามาก นี่คือที่ที่คุณควรตัดสินใจที่จะจัดเก็บ / ไม่จัดเก็บเส้นทางซึ่งฉันจะทำโดยการตรวจสอบอาร์เรย์ของเส้นทางที่คุณต้องการเก็บไว้route.routeConfig.pathและส่งคืนเท็จหากpathไม่มีอยู่ในอาร์เรย์
  3. หากshouldDetachผลตอบแทนtrue, storeเป็นเชื้อเพลิงซึ่งเป็นโอกาสสำหรับคุณที่จะเก็บข้อมูลสิ่งที่คุณต้องการเกี่ยวกับเส้นทาง ไม่ว่าคุณจะทำอะไรคุณจะต้องจัดเก็บDetachedRouteHandleเพราะนั่นคือสิ่งที่ Angular ใช้เพื่อระบุส่วนประกอบที่จัดเก็บของคุณในภายหลัง ด้านล่างผมเก็บทั้งสองDetachedRouteHandleและActivatedRouteSnapshotเป็นตัวแปรท้องถิ่นในชั้นเรียนของฉัน

เราได้เห็นตรรกะสำหรับการจัดเก็บแล้ว แต่การนำทางไปยังส่วนประกอบล่ะ Angular ตัดสินใจที่จะสกัดกั้นการนำทางของคุณและวางระบบที่จัดเก็บไว้ในตำแหน่งได้อย่างไร

  1. อีกครั้งหลังจากshouldReuseRouteกลับมาfalseแล้วให้shouldAttachรันซึ่งเป็นโอกาสของคุณที่จะคิดออกว่าคุณต้องการสร้างใหม่หรือใช้ส่วนประกอบในหน่วยความจำ หากคุณต้องการใช้ส่วนประกอบที่เก็บไว้กลับมาใช้ใหม่trueและคุณก็พร้อมแล้ว!
  2. ตอนนี้เชิงมุมจะขอให้คุณ "ซึ่งองค์ประกอบใดที่คุณต้องการให้เราใช้?" ซึ่งคุณจะแสดงโดยกลับว่าส่วนประกอบจากDetachedRouteHandleretrieve

นั่นคือตรรกะทั้งหมดที่คุณต้องการ! ในโค้ดreuse-strategy.tsด้านล่างนี้ฉันได้ฝากฟังก์ชันดีๆไว้ให้คุณซึ่งจะเปรียบเทียบวัตถุสองชิ้น ฉันจะใช้มันเพื่อเปรียบเทียบเส้นทางในอนาคตของroute.paramsและroute.queryParamsมีที่เก็บไว้หนึ่งของ หากสิ่งเหล่านี้ตรงกันทั้งหมดฉันต้องการใช้ส่วนประกอบที่จัดเก็บไว้แทนที่จะสร้างใหม่ แต่จะทำอย่างไรนั้นขึ้นอยู่กับคุณ!

reuse-strategy.ts

/**
 * reuse-strategy.ts
 * by corbfon 1/6/17
 */

import { ActivatedRouteSnapshot, RouteReuseStrategy, DetachedRouteHandle } from '@angular/router';

/** Interface for object which can store both: 
 * An ActivatedRouteSnapshot, which is useful for determining whether or not you should attach a route (see this.shouldAttach)
 * A DetachedRouteHandle, which is offered up by this.retrieve, in the case that you do want to attach the stored route
 */
interface RouteStorageObject {
    snapshot: ActivatedRouteSnapshot;
    handle: DetachedRouteHandle;
}

export class CustomReuseStrategy implements RouteReuseStrategy {

    /** 
     * Object which will store RouteStorageObjects indexed by keys
     * The keys will all be a path (as in route.routeConfig.path)
     * This allows us to see if we've got a route stored for the requested path
     */
    storedRoutes: { [key: string]: RouteStorageObject } = {};

    /** 
     * Decides when the route should be stored
     * If the route should be stored, I believe the boolean is indicating to a controller whether or not to fire this.store
     * _When_ it is called though does not particularly matter, just know that this determines whether or not we store the route
     * An idea of what to do here: check the route.routeConfig.path to see if it is a path you would like to store
     * @param route This is, at least as I understand it, the route that the user is currently on, and we would like to know if we want to store it
     * @returns boolean indicating that we want to (true) or do not want to (false) store that route
     */
    shouldDetach(route: ActivatedRouteSnapshot): boolean {
        let detach: boolean = true;
        console.log("detaching", route, "return: ", detach);
        return detach;
    }

    /**
     * Constructs object of type `RouteStorageObject` to store, and then stores it for later attachment
     * @param route This is stored for later comparison to requested routes, see `this.shouldAttach`
     * @param handle Later to be retrieved by this.retrieve, and offered up to whatever controller is using this class
     */
    store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
        let storedRoute: RouteStorageObject = {
            snapshot: route,
            handle: handle
        };

        console.log( "store:", storedRoute, "into: ", this.storedRoutes );
        // routes are stored by path - the key is the path name, and the handle is stored under it so that you can only ever have one object stored for a single path
        this.storedRoutes[route.routeConfig.path] = storedRoute;
    }

    /**
     * Determines whether or not there is a stored route and, if there is, whether or not it should be rendered in place of requested route
     * @param route The route the user requested
     * @returns boolean indicating whether or not to render the stored route
     */
    shouldAttach(route: ActivatedRouteSnapshot): boolean {

        // this will be true if the route has been stored before
        let canAttach: boolean = !!route.routeConfig && !!this.storedRoutes[route.routeConfig.path];

        // this decides whether the route already stored should be rendered in place of the requested route, and is the return value
        // at this point we already know that the paths match because the storedResults key is the route.routeConfig.path
        // so, if the route.params and route.queryParams also match, then we should reuse the component
        if (canAttach) {
            let willAttach: boolean = true;
            console.log("param comparison:");
            console.log(this.compareObjects(route.params, this.storedRoutes[route.routeConfig.path].snapshot.params));
            console.log("query param comparison");
            console.log(this.compareObjects(route.queryParams, this.storedRoutes[route.routeConfig.path].snapshot.queryParams));

            let paramsMatch: boolean = this.compareObjects(route.params, this.storedRoutes[route.routeConfig.path].snapshot.params);
            let queryParamsMatch: boolean = this.compareObjects(route.queryParams, this.storedRoutes[route.routeConfig.path].snapshot.queryParams);

            console.log("deciding to attach...", route, "does it match?", this.storedRoutes[route.routeConfig.path].snapshot, "return: ", paramsMatch && queryParamsMatch);
            return paramsMatch && queryParamsMatch;
        } else {
            return false;
        }
    }

    /** 
     * Finds the locally stored instance of the requested route, if it exists, and returns it
     * @param route New route the user has requested
     * @returns DetachedRouteHandle object which can be used to render the component
     */
    retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {

        // return null if the path does not have a routerConfig OR if there is no stored route for that routerConfig
        if (!route.routeConfig || !this.storedRoutes[route.routeConfig.path]) return null;
        console.log("retrieving", "return: ", this.storedRoutes[route.routeConfig.path]);

        /** returns handle when the route.routeConfig.path is already stored */
        return this.storedRoutes[route.routeConfig.path].handle;
    }

    /** 
     * Determines whether or not the current route should be reused
     * @param future The route the user is going to, as triggered by the router
     * @param curr The route the user is currently on
     * @returns boolean basically indicating true if the user intends to leave the current route
     */
    shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
        console.log("deciding to reuse", "future", future.routeConfig, "current", curr.routeConfig, "return: ", future.routeConfig === curr.routeConfig);
        return future.routeConfig === curr.routeConfig;
    }

    /** 
     * This nasty bugger finds out whether the objects are _traditionally_ equal to each other, like you might assume someone else would have put this function in vanilla JS already
     * One thing to note is that it uses coercive comparison (==) on properties which both objects have, not strict comparison (===)
     * Another important note is that the method only tells you if `compare` has all equal parameters to `base`, not the other way around
     * @param base The base object which you would like to compare another object to
     * @param compare The object to compare to base
     * @returns boolean indicating whether or not the objects have all the same properties and those properties are ==
     */
    private compareObjects(base: any, compare: any): boolean {

        // loop through all properties in base object
        for (let baseProperty in base) {

            // determine if comparrison object has that property, if not: return false
            if (compare.hasOwnProperty(baseProperty)) {
                switch(typeof base[baseProperty]) {
                    // if one is object and other is not: return false
                    // if they are both objects, recursively call this comparison function
                    case 'object':
                        if ( typeof compare[baseProperty] !== 'object' || !this.compareObjects(base[baseProperty], compare[baseProperty]) ) { return false; } break;
                    // if one is function and other is not: return false
                    // if both are functions, compare function.toString() results
                    case 'function':
                        if ( typeof compare[baseProperty] !== 'function' || base[baseProperty].toString() !== compare[baseProperty].toString() ) { return false; } break;
                    // otherwise, see if they are equal using coercive comparison
                    default:
                        if ( base[baseProperty] != compare[baseProperty] ) { return false; }
                }
            } else {
                return false;
            }
        }

        // returns true only after false HAS NOT BEEN returned through all loops
        return true;
    }
}

พฤติกรรม

การใช้งานนี้จะจัดเก็บทุกเส้นทางที่ไม่ซ้ำกันที่ผู้ใช้เข้าชมบนเราเตอร์ทุกครั้ง สิ่งนี้จะเพิ่มไปยังส่วนประกอบที่เก็บไว้ในหน่วยความจำตลอดทั้งเซสชันของผู้ใช้บนไซต์ หากคุณต้องการ จำกัด เส้นทางที่คุณจัดเก็บสถานที่ที่จะทำคือshouldDetachวิธีการ มันควบคุมเส้นทางที่คุณบันทึก

ตัวอย่าง

สมมติว่าผู้ใช้ของคุณค้นหาบางสิ่งจากหน้าแรกซึ่งจะนำทางพวกเขาไปยังเส้นทางsearch/:termซึ่งอาจปรากฏในลักษณะwww.yourwebsite.com/search/thingsearchedforนี้ หน้าการค้นหามีผลการค้นหามากมาย คุณต้องการเก็บเส้นทางนี้ไว้เผื่อพวกเขาต้องการกลับมาอีก! ตอนนี้พวกเขาคลิกผลการค้นหาและเข้าสู่ระบบview/:resultIdซึ่งคุณไม่ต้องการจัดเก็บเนื่องจากพวกเขาอาจจะอยู่ที่นั่นเพียงครั้งเดียว ด้วยการใช้งานข้างต้นฉันจะเปลี่ยนshouldDetachวิธีการ! นี่คือสิ่งที่อาจมีลักษณะดังนี้:

ก่อนอื่นมาสร้างอาร์เรย์ของเส้นทางที่เราต้องการจัดเก็บ

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

ตอนนี้shouldDetachเราสามารถตรวจสอบroute.routeConfig.pathกับอาร์เรย์ของเราได้

shouldDetach(route: ActivatedRouteSnapshot): boolean {
    // check to see if the route's path is in our acceptedRoutes array
    if (this.acceptedRoutes.indexOf(route.routeConfig.path) > -1) {
        console.log("detaching", route);
        return true;
    } else {
        return false; // will be "view/:resultId" when user navigates to result
    }
}

เนื่องจาก Angular จะจัดเก็บเส้นทางเดียวเท่านั้นพื้นที่จัดเก็บนี้จึงมีน้ำหนักเบาและเราจะจัดเก็บเฉพาะส่วนประกอบที่อยู่ที่search/:termไม่ใช่ที่อื่นทั้งหมด!

ลิงค์เพิ่มเติม

แม้ว่าจะยังไม่มีเอกสารมากนัก แต่ลิงก์ไปยังสิ่งที่มีอยู่มีดังนี้:

เอกสารเชิงมุม: https://angular.io/docs/ts/latest/api/router/index/RouteReuseStrategy-class.html

บทความแนะนำ: https://www.softwarearchitekt.at/post/2016/12/02/sticky-routes-in-angular-2-3-with-routereusestrategy.aspx

การใช้งานRouteReuseStrategyเริ่มต้นของ nativescript-angular : https://github.com/NativeScript/nativescript-angular/blob/cb4fd3a/nativescript-angular/router/ns-route-reuse-strategy.ts


2
@shaahin ฉันได้เพิ่มตัวอย่างซึ่งเป็นรหัสที่แน่นอนที่มีอยู่ในการใช้งานปัจจุบันของฉัน!
Corbfon

1
@Corbfon ฉันได้เปิดปัญหาในหน้า github อย่างเป็นทางการ: github.com/angular/angular/issues/13869
Tjaart van der Walt

2
มีวิธีทำให้แอนิเมชั่นรีรันอีกครั้งเมื่อเปิดใช้งานเส้นทางที่เก็บไว้อีกครั้งหรือไม่?
Jinder Sidhu

2
ReuseRouteStrategy จะส่งส่วนประกอบของคุณกลับไปที่เราเตอร์ดังนั้นส่วนประกอบจะอยู่ในสถานะใดก็ได้ที่เหลืออยู่หากคุณต้องการให้ส่วนประกอบตอบสนองต่อสิ่งที่แนบมาคุณอาจใช้บริการที่มีObservableไฟล์. คอมโพเนนต์ควรสมัครเป็นสมาชิกในObservableช่วงอายุการngOnInitใช้งาน hook จากนั้นคุณจะสามารถบอกส่วนประกอบได้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 },
  ]})

แชร์ / Routing.ts:

export class CustomReuseStrategy implements RouteReuseStrategy {
 routesToCache: string[] = ["dashboard"];
 storedRouteHandles = new Map<string, DetachedRouteHandle>();

 // Decides if the route should be stored
 shouldDetach(route: ActivatedRouteSnapshot): boolean {
    return this.routesToCache.indexOf(route.routeConfig.path) > -1;
 }

 //Store the information for the route we're destructing
 store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
    this.storedRouteHandles.set(route.routeConfig.path, handle);
 }

//Return true if we have a stored route object for the next route
 shouldAttach(route: ActivatedRouteSnapshot): boolean {
    return this.storedRouteHandles.has(route.routeConfig.path);
 }

 //If we returned true in shouldAttach(), now return the actual route data for restoration
 retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
    return this.storedRouteHandles.get(route.routeConfig.path);
 }

 //Reuse the route if we're going to and from the same route
 shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    return future.routeConfig === curr.routeConfig;
 }
}

1
วิธีนี้จะใช้ได้กับเส้นทางที่ขี้เกียจโหลดหรือไม่
bluePearl

@bluePearl ตรวจสอบคำตอบด้านล่าง
Chris Fremgen

2
routeConfig เป็นโมฆะสำหรับเส้นทางที่แตกต่างกันดังนั้น shouldReuseRoute จะคืนค่าจริงซึ่งไม่ใช่พฤติกรรมที่ต้องการเสมอ
Gil Epshtain

20

นอกเหนือจากคำตอบที่ได้รับการยอมรับ (โดย 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

ฉันทำให้ฉันสับสนแบบนี้ข้างต้นไม่ได้ผลมันจะระเบิดทันทีที่ฉันไปถึงเส้นทางแคชหนึ่งของฉัน (มันจะไม่นำทางอีกต่อไปและมีข้อผิดพลาดในคอนโซล) ดูเหมือนว่าโซลูชันของ Chris Fremgen จะทำงานได้ดีกับโมดูลที่ขี้เกียจของฉันเท่าที่ฉันสามารถบอกได้ ...
MIP1983

12

การใช้งานอื่นที่ถูกต้องสมบูรณ์และใช้ซ้ำได้มากขึ้น อันนี้รองรับโมดูลที่โหลดแบบขี้เกียจเป็น @ UğurDinçและรวมแฟล็กข้อมูลเส้นทาง @Davour การปรับปรุงที่ดีที่สุดคือการสร้างตัวระบุที่ไม่ซ้ำ (เกือบ) โดยอัตโนมัติตามเส้นทางสัมบูรณ์ของหน้า วิธีนี้คุณไม่ต้องกำหนดเองในทุกหน้า

ทำเครื่องหมายหน้าใด ๆ reuseRoute: trueที่คุณต้องการที่จะตั้งค่าแคช จะใช้ในshouldDetachวิธีการ

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

อันนี้เป็นการใช้กลยุทธ์ที่ง่ายที่สุดโดยไม่ต้องเปรียบเทียบพารามิเตอร์ข้อความค้นหา

import { ActivatedRouteSnapshot, RouteReuseStrategy, DetachedRouteHandle, UrlSegment } from '@angular/router'

export class CustomReuseStrategy implements RouteReuseStrategy {

  storedHandles: { [key: string]: DetachedRouteHandle } = {};

  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    return route.data.reuseRoute || false;
  }

  store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
    const id = this.createIdentifier(route);
    if (route.data.reuseRoute) {
      this.storedHandles[id] = handle;
    }
  }

  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    const id = this.createIdentifier(route);
    const handle = this.storedHandles[id];
    const canAttach = !!route.routeConfig && !!handle;
    return canAttach;
  }

  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
    const id = this.createIdentifier(route);
    if (!route.routeConfig || !this.storedHandles[id]) return null;
    return this.storedHandles[id];
  }

  shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    return future.routeConfig === curr.routeConfig;
  }

  private createIdentifier(route: ActivatedRouteSnapshot) {
    // Build the complete path from the root to the input route
    const segments: UrlSegment[][] = route.pathFromRoot.map(r => r.url);
    const subpaths = ([] as UrlSegment[]).concat(...segments).map(segment => segment.path);
    // Result: ${route_depth}-${path}
    return segments.length + '-' + subpaths.join('/');
  }
}

อันนี้เปรียบเทียบพารามิเตอร์ของแบบสอบถามด้วย compareObjectsมีการปรับปรุงเล็กน้อยในเวอร์ชัน @Corbfon: วนซ้ำคุณสมบัติของทั้งสองฐานและเปรียบเทียบวัตถุ โปรดจำไว้ว่าคุณสามารถใช้การใช้งานภายนอกและเชื่อถือได้มากขึ้นเช่นisEqualวิธีการlodash

import { ActivatedRouteSnapshot, RouteReuseStrategy, DetachedRouteHandle, UrlSegment } from '@angular/router'

interface RouteStorageObject {
  snapshot: ActivatedRouteSnapshot;
  handle: DetachedRouteHandle;
}

export class CustomReuseStrategy implements RouteReuseStrategy {

  storedRoutes: { [key: string]: RouteStorageObject } = {};

  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    return route.data.reuseRoute || false;
  }

  store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
    const id = this.createIdentifier(route);
    if (route.data.reuseRoute && id.length > 0) {
      this.storedRoutes[id] = { handle, snapshot: route };
    }
  }

  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    const id = this.createIdentifier(route);
    const storedObject = this.storedRoutes[id];
    const canAttach = !!route.routeConfig && !!storedObject;
    if (!canAttach) return false;

    const paramsMatch = this.compareObjects(route.params, storedObject.snapshot.params);
    const queryParamsMatch = this.compareObjects(route.queryParams, storedObject.snapshot.queryParams);

    console.log('deciding to attach...', route, 'does it match?');
    console.log('param comparison:', paramsMatch);
    console.log('query param comparison', queryParamsMatch);
    console.log(storedObject.snapshot, 'return: ', paramsMatch && queryParamsMatch);

    return paramsMatch && queryParamsMatch;
  }

  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
    const id = this.createIdentifier(route);
    if (!route.routeConfig || !this.storedRoutes[id]) return null;
    return this.storedRoutes[id].handle;
  }

  shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    return future.routeConfig === curr.routeConfig;
  }

  private createIdentifier(route: ActivatedRouteSnapshot) {
    // Build the complete path from the root to the input route
    const segments: UrlSegment[][] = route.pathFromRoot.map(r => r.url);
    const subpaths = ([] as UrlSegment[]).concat(...segments).map(segment => segment.path);
    // Result: ${route_depth}-${path}
    return segments.length + '-' + subpaths.join('/');
  }

  private compareObjects(base: any, compare: any): boolean {

    // loop through all properties
    for (const baseProperty in { ...base, ...compare }) {

      // determine if comparrison object has that property, if not: return false
      if (compare.hasOwnProperty(baseProperty)) {
        switch (typeof base[baseProperty]) {
          // if one is object and other is not: return false
          // if they are both objects, recursively call this comparison function
          case 'object':
            if (typeof compare[baseProperty] !== 'object' || !this.compareObjects(base[baseProperty], compare[baseProperty])) {
              return false;
            }
            break;
          // if one is function and other is not: return false
          // if both are functions, compare function.toString() results
          case 'function':
            if (typeof compare[baseProperty] !== 'function' || base[baseProperty].toString() !== compare[baseProperty].toString()) {
              return false;
            }
            break;
          // otherwise, see if they are equal using coercive comparison
          default:
            // tslint:disable-next-line triple-equals
            if (base[baseProperty] != compare[baseProperty]) {
              return false;
            }
        }
      } else {
        return false;
      }
    }

    // returns true only after false HAS NOT BEEN returned through all loops
    return true;
  }
}

หากคุณมีวิธีที่ดีที่สุดในการสร้างคีย์เฉพาะแสดงความคิดเห็นคำตอบของฉันฉันจะอัปเดตรหัส

ขอบคุณทุกคนที่แบ่งปันวิธีแก้ปัญหาของพวกเขา


3
นี่ควรเป็นคำตอบที่ได้รับการยอมรับ โซลูชันหลายอย่างที่ให้ไว้ข้างต้นไม่สามารถรองรับหลายเพจที่มี URL ย่อยเดียวกัน เนื่องจากกำลังเปรียบเทียบ URL ที่เปิดใช้งานซึ่งไม่ใช่เส้นทางแบบเต็ม
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

1

ฉันประสบกับปัญหาเหล่านี้ในการใช้กลยุทธ์การใช้เส้นทางที่กำหนดเอง:

  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.