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