如何实现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 { }
最后一部分是写这个类,它将控制是否分离,存储,检索和重新连接路由。 在我们了解它们之前,我们在这里做一个简单的机制解释。 参考下面的代码来描述我所描述的方法,当然, 代码中有很多文档。
- 当您浏览时,
shouldReuseRoute
会触发。 这个对我来说有点奇怪,但是如果它返回true
,那么它实际上会重用你当前的路线,而其他的方法都不会被激发。 如果用户正在导航,我只是返回false。 - 如果
shouldReuseRoute
返回false
,则shouldDetach
触发。shouldDetach
确定是否要存储路由,并返回一个表示同样多的boolean
。 这是您应该决定存储/不存储path的位置 ,我将通过检查要存储在route.routeConfig.path
的path数组来route.routeConfig.path
,如果path
不存在于数组中,则返回false。 - 如果
shouldDetach
返回true
,store
被解雇,这是一个机会,您可以存储您想要的任何路线信息。 无论你做什么,你都需要存储DetachedRouteHandle
因为这是Angular以后用来识别你的存储组件的原因。 下面,我将DetachedRouteHandle
和ActivatedRouteSnapshot
都存储到我的类的局部variables中。
所以,我们已经看到了存储的逻辑,但是如何导航到组件呢? Angular是如何决定拦截你的导航,并把它存储的地方?
- 同样,在
shouldReuseRoute
返回false
,shouldAttach
将运行,这是您计算是否要在内存中重新生成或使用组件的机会。 如果你想重用一个存储的组件,返回true
,你很好! - 现在Angular会问你,“你想让我们使用哪个组件?”,你将通过返回组件的
DetachedRouteHandle
来retrieve
。
这几乎是你所需要的所有逻辑! 在下面的reuse-strategy.ts
的代码中,我也给你留下了一个漂亮的函数来比较两个对象。 我用它来比较未来路由的route.params
和route.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; } }