[解決済み] Angular 2で特定のルートに対してRouteReuseStrategy shouldDetachを実装する方法
質問
ルーティングを実装したAngular 2のモジュールがあり、ナビゲート時に状態を保存してほしい。
ユーザーができるようにする。
- 検索式」を使って文書を検索する
- 結果の1つに移動する
- サーバーと通信せずに、'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 {
}
最後の作品 は、ルートが切り離され、保存され、取得され、そして再接続されるかどうかを制御するクラスを書くことです。その前に、古い コピー&ペースト ここでは、私が理解している範囲で、簡単な仕組みの説明をします。私が説明している方法については、以下のコードを参照してください。もちろん、たくさんのドキュメントがあります。 コード内 .
-
ナビゲートするとき
shouldReuseRoute
を発射します。これは少し奇妙なことですが もしそれがtrue
を実行すると、実際に現在いるルートが再利用され、他のメソッドは何も実行されません。ユーザーが離れていく場合は、falseを返すだけです。 -
もし
shouldReuseRoute
を返します。false
,shouldDetach
を発射します。shouldDetach
はルートを保存するかどうかを決定し、その結果をboolean
であることを示す。 ここで、パスを保存する/しないを決定します。 のパスの配列をチェックすることによって行います。 欲しい に対して保存されます。route.routeConfig.path
の場合、false を返します。path
が配列に存在しない場合。 -
もし
shouldDetach
が返されます。true
,store
が発生したら、ルートに関する好きな情報を保存するチャンスです。どのような場合でもDetachedRouteHandle
これはAngularが後で保存されたコンポーネントを識別するために使用するものだからです。以下、私はDetachedRouteHandle
とActivatedRouteSnapshot
を私のクラスのローカル変数にコピーします。
さて、ストレージのロジックは確認できましたが、ナビゲーションのロジックはどうでしょうか。 への コンポーネント?Angularはどのようにナビゲーションを中断して、保存されたものをその場所に置くことを決定するのでしょうか?
-
ここでも
shouldReuseRoute
が返されました。false
,shouldAttach
が実行されます。これは、メモリ内のコンポーネントを再生成したいのか、それとも使用したいのかを判断するチャンスです。もし保存されているコンポーネントを再利用したい場合はtrue
で、順調に進んでいます。 -
Angularはどのコンポーネントを使うか聞いてきますので、そのコンポーネントの
DetachedRouteHandle
からretrieve
.
これで、必要なロジックはほとんど揃いましたね。のコードでは
reuse-strategy.ts
また、2つのオブジェクトを比較する便利な関数も用意しました。これを使って、未来のルートの
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は
のインスタンスを1つだけ保存します。
にあるコンポーネントのみを保存することになります。
search/:term
であり、他のすべてではありません!
追加リンク
まだ多くのドキュメントが存在しませんが、存在するドキュメントへのリンクをいくつか紹介します。
Angular Docs。 https://angular.io/docs/ts/latest/api/router/index/RouteReuseStrategy-class.html
nativescript-angular のデフォルトの実装である RouteReuseStrategy : https://github.com/NativeScript/nativescript-angular/blob/cb4fd3a/nativescript-angular/router/ns-route-reuse-strategy.ts
関連
-
[解決済み】"フォームが接続されていないため、フォームの送信がキャンセルされました "というエラーの取得について
-
[解決済み] TypeError: $.ajax(...) is not a function?
-
[解決済み】React Uncaught Error: 対象コンテナが DOM 要素でない [重複]。
-
[解決済み] 配列から特定の項目を削除するにはどうすればよいですか?
-
[解決済み] JavaScriptのオブジェクトが空であることをテストするにはどうすればよいですか?
-
[解決済み] 配列に特定のインデックスで項目を挿入する方法 (JavaScript)
-
[解決済み] JavaScriptで空文字列/未定義文字列/null文字列をチェックするにはどうすればよいですか?
-
[解決済み] 特定の行のeslintルールをオフにする
-
[解決済み] JavaScript でオブジェクトが特定のプロパティを持つかどうかを確認するにはどうすればよいですか?
-
[解決済み] Angularプロジェクトごとに生成される膨大な数のファイル
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
vue3.0プロジェクトのアーキテクチャを構築するための便利なツール
-
HTML+CSS+JavaScriptで簡単な三目並べゲームを作成する。
-
vueはopenlayersを使用してスカイマップとガオードマップをロードする
-
vueが定義するプライベートフィルタと基本的な使い方
-
Vueでルートネスティングを実装する例
-
Vueのイベント処理とイベントモディファイアの解説
-
[解決済み] 期待される代入または関数呼び出し: 未使用式なし ReactJS
-
[解決済み】TypeErrorの解決方法。未定義またはヌルをオブジェクトに変換できない
-
[解決済み】React - TypeError: 未定義のプロパティ 'props' を読み取ることができない。
-
[解決済み】JavaScriptでインラインIF文の書き方は?