如何实现RouteReuseStrategy应该针对Angular 2中的特定路线进行定义

我有一个Angular 2模块,我已经实现了路由,并希望导航时存储的状态。 用户应该能够:1.使用searchformulasearch文档2.导航到其中一个结果3.导航回searchresult – 不与服务器通信

这可能包括RouteReuseStrategy。 问题是:如何实现文档不应该被存储?

所以pathpath“文件”的状态应该被存储,并且pathpath“文件/:id”状态不应该被存储?

嘿安德斯,很好的问题!

我和你有几乎相同的用例,也想做同样的事情! 用户search>获得结果>用户导航结果>用户导航返回> BOOM 快速返回结果 ,但不希望存储用户导航到的特定结果。

TL;博士

您需要有一个实现RouteReuseStrategy的类,并在ngModule提供您的策略。 如果要在路由存储时进行修改,请修改shouldDetach函数。 当它返回true ,Angular存储路由。 如果您想修改路线附加时,修改shouldAttach函数。 当shouldAttach返回true时,Angular将使用存储的路线代替请求的路线。 这里有一个Plunker让你玩。

关于RouteReuseStrategy

通过问这个问题,你已经明白了,RouteReuseStrategy允许你告诉Angular2 不要销毁一个组件,而是实际上保存它以便在以后重新渲染。 这很酷,因为它允许:

  • 减less服务器调用
  • 提高速度
  • 并且默认情况下,组件呈现的是相同的状态

如果你想临时离开一个页面,即使用户input了大量文本,最后一个也是很重要的。 由于表单数量过多 ,企业应用程序会喜欢这个function!

这是我想出来解决这个问题。 正如你所说的,你需要使用3.4.1及更高版本中由@ angular / router提供的RouteReuseStrategy

去做

首先确保您的项目具有@ angular / router版本3.4.1或更高版本。

接下来 ,创build一个文件,它将容纳实现RouteReuseStrategy的类。 我打电话给mine reuse-strategy.ts并将其放在/app文件夹中以保存。 现在,这个类应该是这样的:

 import { RouteReuseStrategy } from '@angular/router'; export class CustomReuseStrategy implements RouteReuseStrategy { } 

(不要担心你的TypeScript错误,我们即将解决所有问题)

通过向app.module提供类来完成基础 app.module 。 请注意,您尚未编写CustomReuseStrategy ,但应继续并从reuse-strategy.ts import它。 还import { RouteReuseStrategy } from '@angular/router';

 @NgModule({ [...], providers: [ {provide: RouteReuseStrategy, useClass: CustomReuseStrategy} ] )} export class AppModule { } 

最后一部分是写这个类,它将控制是否分离,存储,检索和重新连接路由。 在我们了解它们之前,我们在这里做一个简单的机制解释。 参考下面的代码来描述我所描述的方法,当然, 代码中有很多文档。

  1. 当您浏览时, shouldReuseRoute会触发。 这个对我来说有点奇怪,但是如果它返回true ,那么它实际上会重用你当前的路线,而其他的方法都不会被激发。 如果用户正在导航,我只是返回false。
  2. 如果shouldReuseRoute返回false ,则shouldDetach触发。 shouldDetach确定是否要存储路由,并返回一个表示同样多的boolean这是您应该决定存储/不存储path的位置 ,我将通过检查存储在route.routeConfig.path的path数组来route.routeConfig.path ,如果path不存在于数组中,则返回false。
  3. 如果shouldDetach返回truestore被解雇,这是一个机会,您可以存储您想要的任何路线信息。 无论你做什么,你都需要存储DetachedRouteHandle因为这是Angular以后用来识别你的存储组件的原因。 下面,我将DetachedRouteHandleActivatedRouteSnapshot都存储到我的类的局部variables中。

所以,我们已经看到了存储的逻辑,但是如何导航组件呢? Angular是如何决定拦截你的导航,并把它存储的地方?

  1. 同样,在shouldReuseRoute返回falseshouldAttach将运行,这是您计算是否要在内存中重新生成或使用组件的机会。 如果你想重用一个存储的组件,返回true ,你很好!
  2. 现在Angular会问你,“你想让我们使用哪个组件?”,你将通过返回组件的DetachedRouteHandleretrieve

这几乎是你所需要的所有逻辑! 在下面的reuse-strategy.ts的代码中,我也给你留下了一个漂亮的函数来比较两个对象。 我用它来比较未来路由的route.paramsroute.queryParams与存储的路由。 如果这些都匹配,我想使用存储的组件,而不是生成一个新的。 但是你怎么做取决于你呢!

重用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某些内容,然后将其导航到pathsearch/:term ,这可能类似于www.yourwebsite.com/search/thingsearchedfor 。 search页面包含一堆search结果。 你想存储这条路线,以防他们想要回来! 现在,他们点击一个search结果,然后导航到view/:resultId ,你不想存储,因为他们可能只会在那里只有一次。 有了上面的实现,我只需要改变shouldDetach方法! 这可能是这样的:

首先让我们制作一系列我们想要存储的path。

 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 只存储路由的一个实例 ,因此这个存储将是轻量级的,我们只会存储位于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

不要被接受的答案吓倒,这是非常简单的。 这里有一个你需要的快速回答。 我会build议至less阅读接受的答案,因为它是非常详细的。

这个解决scheme不会像接受的答案那样进行任何参数比较,但是对于存储一组路由来说,它可以正常工作。

app.module.tsimport:

 import { RouteReuseStrategy } from '@angular/router'; import { CustomReuseStrategy, Routing } from './shared/routing'; @NgModule({ //... providers: [ { provide: RouteReuseStrategy, useClass: CustomReuseStrategy }, ]}) 

共享/ 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; } }