Hai Anders, pertanyaan bagus!
Saya memiliki kasus penggunaan yang hampir sama dengan Anda, dan ingin melakukan hal yang sama! Pencarian pengguna> dapatkan hasil> Pengguna menavigasi ke hasil> Pengguna menavigasi kembali> BOOM dengan cepat kembali ke hasil , tetapi Anda tidak ingin menyimpan hasil spesifik yang dinavigasi oleh pengguna.
tl; dr
Anda perlu memiliki kelas yang mengimplementasikan RouteReuseStrategy
dan menyediakan strategi Anda di ngModule
. Jika Anda ingin mengubah kapan rute disimpan, ubah shouldDetach
fungsinya. Saat kembali true
, Angular menyimpan rute tersebut. Jika Anda ingin mengubah saat rute dipasang, ubah shouldAttach
fungsinya. Saat shouldAttach
mengembalikan nilai true, Angular akan menggunakan rute yang disimpan sebagai pengganti rute yang diminta. Ini Plunker untuk Anda mainkan.
Tentang RouteReuseStrategy
Dengan menanyakan pertanyaan ini, Anda sudah memahami bahwa RouteReuseStrategy memungkinkan Anda memberi tahu Angular untuk tidak menghancurkan komponen, tetapi sebenarnya menyimpannya untuk dirender ulang di lain waktu. Itu keren karena memungkinkan:
- Panggilan server menurun
- Kecepatan ditingkatkan
- DAN komponen membuat, secara default, dalam keadaan yang sama seperti saat ditinggalkan
Yang terakhir itu penting jika Anda ingin, misalnya, meninggalkan halaman sementara meskipun pengguna telah memasukkan banyak teks ke dalamnya. Aplikasi perusahaan akan menyukai fitur ini karena terlalu banyaknya formulir!
Inilah yang saya pikirkan untuk menyelesaikan masalah. Seperti yang Anda katakan, Anda perlu memanfaatkan yang RouteReuseStrategy
ditawarkan oleh @ angular / router di versi 3.4.1 dan lebih tinggi.
MELAKUKAN
Pertama, Pastikan proyek Anda memiliki @ angular / router versi 3.4.1 atau lebih tinggi.
Selanjutnya , buat file yang akan menampung kelas Anda yang mengimplementasikan RouteReuseStrategy
. Saya menelepon milik saya reuse-strategy.ts
dan meletakkannya di /app
folder untuk diamankan. Untuk saat ini, kelas ini akan terlihat seperti:
import { RouteReuseStrategy } from '@angular/router';
export class CustomReuseStrategy implements RouteReuseStrategy {
}
(jangan khawatir tentang kesalahan TypeScript Anda, kami akan menyelesaikan semuanya)
Selesaikan dasar dengan menyediakan kelas untuk Anda app.module
. Perhatikan bahwa Anda belum ditulis CustomReuseStrategy
, tetapi harus pergi ke depan dan import
dari reuse-strategy.ts
semua sama. Jugaimport { RouteReuseStrategy } from '@angular/router';
@NgModule({
[...],
providers: [
{provide: RouteReuseStrategy, useClass: CustomReuseStrategy}
]
)}
export class AppModule {
}
Bagian terakhir adalah menulis kelas yang akan mengontrol apakah rute dilepas, disimpan, diambil, dan dipasang kembali. Sebelum kita sampai ke copy / paste lama , saya akan melakukan penjelasan singkat tentang mekanika di sini, seperti yang saya pahami. Referensi kode di bawah ini untuk metode yang saya jelaskan, dan tentu saja, ada banyak dokumentasi dalam kode tersebut .
- Saat Anda menavigasi,
shouldReuseRoute
tembak. Yang ini agak aneh bagi saya, tetapi jika kembali true
, maka itu benar-benar menggunakan kembali rute Anda saat ini dan tidak ada metode lain yang diaktifkan. Saya hanya mengembalikan false jika pengguna menavigasi keluar.
- Jika
shouldReuseRoute
kembali false
, shouldDetach
tembak. shouldDetach
menentukan apakah Anda ingin menyimpan rute atau tidak, dan mengembalikan boolean
indikasi sebanyak itu. Di sinilah Anda harus memutuskan untuk menyimpan / tidak menyimpan jalur , yang akan saya lakukan dengan memeriksa larik jalur yang ingin Anda simpan route.routeConfig.path
, dan mengembalikan false jika path
tidak ada dalam larik.
- Jika
shouldDetach
pengembalian true
, store
dipecat, yang merupakan kesempatan bagi Anda untuk menyimpan informasi apa pun yang Anda inginkan tentang rute tersebut. Apa pun yang Anda lakukan, Anda harus menyimpan DetachedRouteHandle
karena itulah yang digunakan Angular untuk mengidentifikasi komponen yang Anda simpan nanti. Di bawah, saya menyimpan the DetachedRouteHandle
dan the ActivatedRouteSnapshot
ke dalam variabel lokal untuk kelas saya.
Jadi, kita telah melihat logika untuk penyimpanan, tapi bagaimana dengan menavigasi ke sebuah komponen? Bagaimana Angular memutuskan untuk mencegat navigasi Anda dan meletakkan yang disimpan di tempatnya?
- Sekali lagi, setelah
shouldReuseRoute
kembali false
, shouldAttach
berjalan, yang merupakan kesempatan Anda untuk mengetahui apakah Anda ingin membuat ulang atau menggunakan komponen dalam memori. Jika Anda ingin menggunakan kembali komponen yang disimpan, kembalikan true
dan Anda sudah siap!
- Sekarang sudut akan meminta Anda, "yang komponen yang Anda ingin kami untuk digunakan?", Yang Anda akan menunjukkan dengan kembali bahwa komponen
DetachedRouteHandle
dari retrieve
.
Cukup banyak logika yang Anda butuhkan! Dalam kode untuk reuse-strategy.ts
, di bawah ini, saya juga meninggalkan Anda fungsi bagus yang akan membandingkan dua objek. Saya menggunakannya untuk membandingkan dengan masa depan ini route.params
dan route.queryParams
dengan disimpan seseorang. Jika semuanya cocok, saya ingin menggunakan komponen yang disimpan daripada membuat yang baru. Tapi bagaimana Anda melakukannya, itu terserah Anda!
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;
}
}
Tingkah laku
Implementasi ini menyimpan setiap rute unik yang dikunjungi pengguna di router tepat satu kali. Ini akan terus menambah komponen yang disimpan dalam memori selama sesi pengguna di situs. Jika Anda ingin membatasi rute yang Anda simpan, tempat untuk melakukannya adalah shouldDetach
metodenya. Ini mengontrol rute mana yang Anda simpan.
Contoh
Misalnya pengguna Anda menelusuri sesuatu dari beranda, yang mengarahkan mereka ke jalur search/:term
, yang mungkin tampak seperti www.yourwebsite.com/search/thingsearchedfor
. Halaman pencarian berisi banyak hasil pencarian. Anda ingin menyimpan rute ini, jika mereka ingin kembali ke sana! Sekarang mereka mengklik hasil pencarian dan dinavigasi ke view/:resultId
, yang tidak ingin Anda simpan, karena mereka mungkin hanya ada sekali. Dengan implementasi di atas, saya hanya akan mengubah shouldDetach
metode! Seperti inilah tampilannya:
Pertama mari kita buat array jalur yang ingin kita simpan.
private acceptedRoutes: string[] = ["search/:term"];
sekarang, di dalam shouldDetach
kita dapat memeriksa route.routeConfig.path
terhadap array kita.
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
}
}
Karena Angular hanya akan menyimpan satu instance dari sebuah rute, penyimpanan ini akan menjadi ringan, dan kami hanya akan menyimpan komponen yang terletak di search/:term
dan tidak semua yang lain!
Tautan Tambahan
Meskipun belum banyak dokumentasi di luar sana, berikut adalah beberapa tautan ke apa yang ada:
Angular Docs: https://angular.io/docs/ts/latest/api/router/index/RouteReuseStrategy-class.html
Artikel Intro: https://www.softwarearchitekt.at/post/2016/12/02/sticky-routes-in-angular-2-3-with-routereusestrategy.aspx
Implementasi default nativescript-angular dari RouteReuseStrategy : https://github.com/NativeScript/nativescript-angular/blob/cb4fd3a/nativescript-angular/router/ns-route-reuse-strategy.ts