Cara menerapkan RouteReuseStrategy shouldDetach untuk rute tertentu di Angular 2

115

Saya memiliki modul Angular 2 di mana saya telah mengimplementasikan perutean dan ingin status disimpan saat menavigasi.
Pengguna harus mampu:

  1. mencari dokumen menggunakan 'rumus pencarian'
  2. arahkan ke salah satu hasil
  3. arahkan kembali ke 'searchresult' - tanpa berkomunikasi dengan server

Ini mungkin termasuk RouteReuseStrategy.
Pertanyaannya adalah:
Bagaimana cara menerapkan bahwa dokumen tidak boleh disimpan?

Jadi jalur rute status "dokumen" harus disimpan dan jalur rute status "documents /: id" 'TIDAK boleh disimpan?

Anders Gram Mygind
sumber

Jawaban:

209

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 RouteReuseStrategydan menyediakan strategi Anda di ngModule. Jika Anda ingin mengubah kapan rute disimpan, ubah shouldDetachfungsinya. Saat kembali true, Angular menyimpan rute tersebut. Jika Anda ingin mengubah saat rute dipasang, ubah shouldAttachfungsinya. Saat shouldAttachmengembalikan 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 RouteReuseStrategyditawarkan 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.tsdan meletakkannya di /appfolder 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 importdari reuse-strategy.tssemua 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 .

  1. Saat Anda menavigasi, shouldReuseRoutetembak. 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.
  2. Jika shouldReuseRoutekembali false, shouldDetachtembak. shouldDetachmenentukan apakah Anda ingin menyimpan rute atau tidak, dan mengembalikan booleanindikasi 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 pathtidak ada dalam larik.
  3. Jika shouldDetachpengembalian true, storedipecat, yang merupakan kesempatan bagi Anda untuk menyimpan informasi apa pun yang Anda inginkan tentang rute tersebut. Apa pun yang Anda lakukan, Anda harus menyimpan DetachedRouteHandlekarena itulah yang digunakan Angular untuk mengidentifikasi komponen yang Anda simpan nanti. Di bawah, saya menyimpan the DetachedRouteHandledan the ActivatedRouteSnapshotke 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?

  1. Sekali lagi, setelah shouldReuseRoutekembali false, shouldAttachberjalan, 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 truedan Anda sudah siap!
  2. Sekarang sudut akan meminta Anda, "yang komponen yang Anda ingin kami untuk digunakan?", Yang Anda akan menunjukkan dengan kembali bahwa komponen DetachedRouteHandledari 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.paramsdan route.queryParamsdengan 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 shouldDetachmetodenya. 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 shouldDetachmetode! Seperti inilah tampilannya:

Pertama mari kita buat array jalur yang ingin kita simpan.

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

sekarang, di dalam shouldDetachkita dapat memeriksa route.routeConfig.pathterhadap 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/:termdan 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

Corbfon
sumber
2
@shaahin Saya telah menambahkan contoh, yang merupakan kode persis yang terkandung dalam implementasi saya saat ini!
Corbfon
1
@Corbfon Saya juga telah membuka masalah di halaman github resmi: github.com/angular/angular/issues/13869
Tjaart van der Walt
2
Apakah ada cara untuk menjalankannya kembali animasi enter saat mengaktifkan kembali rute yang disimpan?
Jinder Sidhu
2
ReuseRouteStrategy akan mengembalikan komponen Anda ke router, sehingga akan tetap dalam status apa pun yang tersisa. Jika Anda ingin komponen bereaksi terhadap lampiran, Anda dapat menggunakan layanan yang menawarkan Observable. Komponen harus berlangganan Observableselama ngOnInitkait siklus hidup. Kemudian Anda akan dapat memberi tahu komponen, dari ReuseRouteStrategy, bahwa ia baru saja dipasang dan komponen dapat mengubah statusnya sesuai kebutuhan.
Corbfon
1
@AndersGramMygind jika jawaban saya memberikan jawaban atas pertanyaan yang Anda ajukan, apakah Anda akan menandainya sebagai jawaban?
Corbfon
45

Jangan terintimidasi oleh jawaban yang diterima, ini sangat mudah. Berikut jawaban cepat yang Anda butuhkan. Saya akan merekomendasikan setidaknya membaca jawaban yang diterima, karena penuh dengan detail yang luar biasa.

Solusi ini tidak melakukan perbandingan parameter apa pun seperti jawaban yang diterima tetapi akan berfungsi dengan baik untuk menyimpan sekumpulan rute.

impor 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;
 }
}
Chris Fremgen
sumber
1
Apakah ini juga berfungsi untuk rute yang dimuat lambat?
bluePearl
@bluePearl Periksa jawaban di bawah
Chris Fremgen
2
routeConfig adalah null, untuk rute yang berbeda, oleh karena itu shouldReuseRoute akan selalu mengembalikan nilai true yang bukan perilaku yang diinginkan
Gil Epshtain
19

Selain jawaban yang diterima (oleh Corbfon) dan penjelasan Chris Fremgen yang lebih pendek dan lugas, saya ingin menambahkan cara penanganan rute yang lebih fleksibel yang harus menggunakan strategi penggunaan kembali.

Kedua jawaban tersebut menyimpan rute yang ingin kita cache dalam array dan kemudian memeriksa apakah jalur rute saat ini ada dalam array atau tidak. Pemeriksaan ini dilakukan dengan shouldDetachmetode.

Saya menemukan pendekatan ini tidak fleksibel karena jika kita ingin mengubah nama rute, kita perlu mengingat untuk juga mengubah nama rute di CustomReuseStrategykelas kita . Kami mungkin lupa untuk mengubahnya atau beberapa pengembang lain di tim kami mungkin memutuskan untuk mengubah nama rute bahkan tanpa mengetahui tentang keberadaan RouteReuseStrategy.

Alih-alih menyimpan rute yang ingin kita cache dalam sebuah array, kita bisa menandainya secara langsung dalam RouterModulemenggunakan dataobjek. Dengan cara ini meskipun kami mengubah nama rute, strategi penggunaan kembali akan tetap diterapkan.

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

Dan kemudian dalam shouldDetachmetode kami memanfaatkannya.

shouldDetach(route: ActivatedRouteSnapshot): boolean {
  return route.data.reuseRoute === true;
}
Davor
sumber
1
Solusi bagus. Ini harus benar-benar dimasukkan ke dalam strategi penggunaan kembali rute sudut standar dengan bendera sederhana seperti yang Anda terapkan.
MIP1983
Jawaban yang bagus. Terima kasih banyak!
claudiomatiasrg
14

Untuk menggunakan strategi Chris Fremgen dengan modul yang dimuat lambat, ubah kelas CustomReuseStrategy menjadi berikut:

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

terakhir, dalam file perutean modul fitur Anda, tentukan kunci Anda:

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

Info selengkapnya di sini .

Uğur Dinç
sumber
1
Terima kasih telah menambahkan ini! Saya harus mencobanya. Itu bahkan bisa memperbaiki beberapa masalah penanganan rute anak yang dihadapi solusi saya.
Corbfon
Saya harus menggunakan route.data["key"]untuk membangun tanpa kesalahan. Tetapi masalah yang saya alami adalah bahwa saya memiliki komponen rute + yang digunakan di dua tempat berbeda. 1. sample/list/itemdan 2. product/id/sample/list/itemketika saya pertama kali memuat salah satu jalur itu memuat baik-baik saja tetapi yang lain melempar kesalahan yang dilampirkan kembali karena saya menyimpan berdasarkan list/itemJadi pekerjaan saya adalah saya menggandakan rute dan membuat beberapa perubahan ke jalur url tetapi menampilkan komponen yang sama. Tidak yakin apakah ada solusi lain untuk itu.
bluePearl
Ini membuat saya bingung, hal di atas tidak akan berfungsi, itu akan meledak segera setelah saya mencapai salah satu rute cache saya, (tidak lagi menavigasi dan di sana ada kesalahan di konsol). Solusi Chris Fremgen tampaknya berfungsi dengan baik dengan modul malas saya sejauh yang saya tahu sejauh ini ...
MIP1983
12

Implementasi lain yang lebih valid, lengkap, dan dapat digunakan kembali. Yang satu ini mendukung modul yang dimuat lambat sebagai @ Uğur Dinç dan mengintegrasikan tanda data rute @Davor. Peningkatan terbaik adalah pembuatan otomatis pengenal (hampir) unik berdasarkan jalur absolut halaman. Dengan cara ini Anda tidak perlu mendefinisikannya sendiri di setiap halaman.

Tandai halaman mana saja yang ingin Anda cache pengaturannya reuseRoute: true. Ini akan digunakan dalam shouldDetachmetode.

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

Ini adalah implementasi strategi paling sederhana, tanpa membandingkan parameter kueri.

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

Yang ini juga membandingkan parameter kueri. compareObjectsmemiliki sedikit peningkatan dibandingkan versi @Corbfon: perulangan melalui properti dari kedua basis dan membandingkan objek. Ingatlah bahwa Anda dapat menggunakan implementasi eksternal dan lebih andal seperti isEqualmetode 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;
  }
}

Jika Anda memiliki cara terbaik untuk menghasilkan kunci unik, komentar jawaban saya, saya akan memperbarui kode.

Terima kasih untuk semua orang yang telah membagikan solusinya.

McGiogen
sumber
3
Ini harus menjadi jawaban yang diterima. Banyak solusi yang diberikan di atas tidak dapat mendukung banyak halaman dengan URL turunan yang sama. Karena mereka membandingkan URL ActivatedRoute, yang bukan path lengkap.
zhuhang.jasper
4

Semua solusi yang disebutkan entah bagaimana tidak cukup dalam kasus kami. Kami memiliki aplikasi bisnis yang lebih kecil dengan:

  1. Halaman pengantar
  2. Halaman masuk
  3. Aplikasi (setelah login)

Persyaratan kami:

  1. Modul yang dimuat lambat
  2. Rute bertingkat
  3. Simpan semua status router / komponen dalam memori di bagian aplikasi
  4. Pilihan untuk menggunakan strategi penggunaan kembali sudut default pada rute tertentu
  5. Menghancurkan semua komponen yang disimpan di memori saat logout

Contoh sederhana dari rute kami:

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

Strategi penggunaan kembali:

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('');
    }
}
hovado
sumber
3

berikut ini adalah pekerjaan! referensi: 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, '_')
    }
}

红兵 伍
sumber
1
Hati-hati, ini menggunakan variabel internal _routerState.
DarkNeuron
@DarkNeuron tidak _routerStatemenyebabkan sesuatu yang berbahaya?
k11k2
2
Tidak, tetapi Google tidak berkewajiban untuk menyimpan variabel itu, karena variabel itu digunakan secara internal dan tidak diekspos di API.
DarkNeuron
saat kita menelepon deleteRouteSnapshot?
k11k2
0

Saya menghadapi masalah ini dalam menerapkan strategi penggunaan ulang rute kustom:

  1. Lakukan operasi pada lampirkan / lepas rute: kelola langganan, pembersihan, dll.;
  2. Pertahankan hanya status rute berparameter terakhir: pengoptimalan memori;
  3. Gunakan kembali komponen, bukan status: kelola negara dengan alat pengelolaan negara.
  4. Kesalahan "Tidak dapat memasang kembali ActivatedRouteSnapshot yang dibuat dari rute berbeda";

Jadi saya menulis perpustakaan untuk memecahkan masalah ini. Library menyediakan layanan dan dekorator untuk kaitkan / lepas dan menggunakan komponen rute untuk menyimpan rute yang terlepas, bukan jalur rute.

Contoh:

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

Perpustakaan: https://www.npmjs.com/package/ng-cache-route-reuse

Stas Amasev
sumber
Hanya menautkan ke perpustakaan atau tutorial Anda sendiri bukanlah jawaban yang bagus. Menautkan ke sana, menjelaskan mengapa itu menyelesaikan masalah, memberikan kode tentang bagaimana melakukannya dan menyangkal bahwa Anda menulisnya membuat jawaban yang lebih baik. Lihat: Apa yang menandakan promosi diri yang "Baik"?
Paul Roub