1. ホーム
  2. javascript

[解決済み] Angular 2で特定のルートに対してRouteReuseStrategy shouldDetachを実装する方法

2022-04-28 22:04:05

質問

ルーティングを実装したAngular 2のモジュールがあり、ナビゲート時に状態を保存してほしい。

ユーザーができるようにする。

  1. 検索式」を使って文書を検索する
  2. 結果の1つに移動する
  3. サーバーと通信せずに、'searchresult' に戻る。

を含めて可能です。 RouteReuseStrategy .

という質問があります。

ドキュメントを保存しないようにするには、どのように実装すればよいですか?

つまり、ルートパス "documents" の状態は保存されるべきで、ルートパス "documents/:id" の状態は保存されるべきではない、ということですね。

解決方法は?

アンダースさん、いい質問ですね。

私もあなたとほぼ同じユースケースを持っていて、同じことをしたかったのです。ユーザーが検索を行い、結果を取得する。 ブーム 結果への復帰が速い しかし、ユーザーがナビゲートした特定の結果を保存する必要はありません。

tl;dr

を実装したクラスを用意する必要があります。 RouteReuseStrategy でストラテジーを提供します。 ngModule . ルートが保存されるタイミングを変更したい場合は、以下のように shouldDetach 関数を使用します。この関数が返す true Angularはルートを保存します。ルートがアタッチされるタイミングを変更したい場合は shouldAttach 関数を使用します。いつ shouldAttach がtrueを返すと、Angularはリクエストされたルートの代わりに保存されたルートを使用します。ここでは プランカー を使って遊んでみてください。

RouteReuseStrategyについて

この質問をされた方は、RouteReuseStrategyを使うとAngularの ではなく コンポーネントを破棄するのではなく、後日再レンダリングのために保存することです。それはクールなことです。

  • 減少 サーバーコール
  • 増加した 速度
  • AND コンポーネントのレンダリングは、デフォルトで元の状態と同じになります。

を入力しても、一時的にページから退出したい場合、最後の1つは重要です。 ロット のテキストを入力することができます。エンタープライズ・アプリケーションは、この機能が大好きです。 過剰な フォームの量

この問題を解決するために思いついたのがこれです。おっしゃるとおり、このような場合には RouteReuseStrategy 3.4.1 以降のバージョンでは @angular/router によって提供されます。

TODO

最初 プロジェクトに @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 を実行すると、実際に現在いるルートが再利用され、他のメソッドは何も実行されません。ユーザーが離れていく場合は、falseを返すだけです。
  2. もし shouldReuseRoute を返します。 false , shouldDetach を発射します。 shouldDetach はルートを保存するかどうかを決定し、その結果を boolean であることを示す。 ここで、パスを保存する/しないを決定します。 のパスの配列をチェックすることによって行います。 欲しい に対して保存されます。 route.routeConfig.path の場合、false を返します。 path が配列に存在しない場合。
  3. もし shouldDetach が返されます。 true , store が発生したら、ルートに関する好きな情報を保存するチャンスです。どのような場合でも DetachedRouteHandle これはAngularが後で保存されたコンポーネントを識別するために使用するものだからです。以下、私は DetachedRouteHandleActivatedRouteSnapshot を私のクラスのローカル変数にコピーします。

さて、ストレージのロジックは確認できましたが、ナビゲーションのロジックはどうでしょうか。 への コンポーネント?Angularはどのようにナビゲーションを中断して、保存されたものをその場所に置くことを決定するのでしょうか?

  1. ここでも shouldReuseRoute が返されました。 false , shouldAttach が実行されます。これは、メモリ内のコンポーネントを再生成したいのか、それとも使用したいのかを判断するチャンスです。もし保存されているコンポーネントを再利用したい場合は true で、順調に進んでいます。
  2. Angularはどのコンポーネントを使うか聞いてきますので、そのコンポーネントの DetachedRouteHandle から retrieve .

これで、必要なロジックはほとんど揃いましたね。のコードでは reuse-strategy.ts また、2つのオブジェクトを比較する便利な関数も用意しました。これを使って、未来のルートの route.paramsroute.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は のインスタンスを1つだけ保存します。 にあるコンポーネントのみを保存することになります。 search/:term であり、他のすべてではありません!

追加リンク

まだ多くのドキュメントが存在しませんが、存在するドキュメントへのリンクをいくつか紹介します。

Angular Docs。 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

nativescript-angular のデフォルトの実装である RouteReuseStrategy : https://github.com/NativeScript/nativescript-angular/blob/cb4fd3a/nativescript-angular/router/ns-route-reuse-strategy.ts