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 RouteReuseStrategy
và 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 shouldDetach
chứ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 shouldAttach
chức năng. Khi shouldAttach
trả 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 độ
- VÀ 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.ts
và đặt nó vào /app
thư 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à import
nó từ reuse-strategy.ts
tấ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ã .
- Khi bạn điều hướng,
shouldReuseRoute
kí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.
- Nếu
shouldReuseRoute
trả lại false
, shouldDetach
cháy. shouldDetach
xác định xem bạn có muốn lưu trữ tuyến đường hay không và trả về một boolean
chỉ 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.path
và trả về false nếu path
không tồn tại trong mảng.
- Nếu bị
shouldDetach
trả lại true
, store
bị 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ữ DetachedRouteHandle
vì đó 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 DetachedRouteHandle
và the ActivatedRouteSnapshot
và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ó?
- Một lần nữa, sau khi
shouldReuseRoute
đã quay trở lại false
, hãy shouldAttach
chạ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 true
và bạn đang trên đường đi của mình!
- 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
DetachedRouteHandle
từ retrieve
.
Đó là khá nhiều logic bạn cần! Trong đoạn mã reuse-strategy.ts
dướ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.params
và route.queryParams
vớ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à shouldDetach
phươ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/:resultId
mà 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 shouldDetach
phươ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 shouldDetach
chúng ta có thể kiểm tra route.routeConfig.path
mả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/:term
chứ 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