我有一个Angular2模块,在其中实现了路由,并希望在导航时存储状态。用户应该能够:1.使用searchformula搜索文档2.导航到结果之一3.导航回到searchresult-无需与服务器通信
这可能包括RouteReuseStrategy。问题是:如何实现不应存储该文档?
因此,应该存储路由路径“ documents”的状态,而不应该存储路由路径“ documents /:id”的状态?
嗨,安德斯,很好的问题!
我有几乎与您相同的用例,并且想做同样的事情!用户搜索>获取结果>用户导航到结果>用户导航> BOOM 迅速恢复结果 ,但是您不希望存储用户导航到的特定结果。
tl; dr
您需要具有一个类,该类在中实现RouteReuseStrategy并提供您的策略ngModule。如果要在存储路径时进行修改,请修改shouldDetach功能。返回时true,Angular将存储路线。如果要在连接路由时进行修改,请修改shouldAttach功能。当shouldAttach返回true时,Angular将使用存储的路线代替请求的路线。这是一个Plunker供您玩耍。
RouteReuseStrategy
ngModule
shouldDetach
true
shouldAttach
关于RouteReuseStrategy
通过询问这个问题,您已经了解到RouteReuseStrategy允许您告诉Angular 不要 破坏组件,而实际上是保存它以便以后重新渲染。这很酷,因为它允许:
如果您想暂时离开某个页面,即使用户在其中输入了 很多 文本,那么最后一个页面也很重要。企业应用程序会喜欢这种功能,因为表单数量 过多 !
这就是我想出的解决问题的方法。如您所说,您需要利用RouteReuseStrategy3.4.1及更高版本中@ angular / router提供的功能。
去做
首先, 请确保您的项目具有@ angular / router 3.4.1或更高版本。
接下来 ,创建一个文件来存放要实现的类RouteReuseStrategy。我打电话给我reuse- strategy.ts,并将其放在/app文件夹中以进行保管。现在,此类应如下所示:
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';
app.module
CustomReuseStrategy
import
reuse-strategy.ts
import { RouteReuseStrategy } from '@angular/router';
@NgModule({ [...], providers: [ {provide: RouteReuseStrategy, useClass: CustomReuseStrategy} ] )} export class AppModule { }
最后一部分 是编写类,该类将控制是否分离,存储,检索和重新连接路由。在我理解旧的 复制/粘贴之前 ,我将在这里对机制进行简短的解释。请参考以下代码,了解我正在描述的方法,当然, 代码中 有大量文档。
shouldReuseRoute
false
boolean
route.routeConfig.path
path
store
DetachedRouteHandle
ActivatedRouteSnapshot
因此,我们已经了解了存储的逻辑,但是导航 到 组件又如何呢?Angular如何决定拦截您的导航并将已存储的导航放置在原处?
retrieve
这几乎就是您需要的所有逻辑!在reuse-strategy.ts下面的代码中,我还为您提供了一个比较两个对象的漂亮函数。我用它来比较将来的路线route.params和route.queryParams已存储的路线。如果所有这些都匹配,我想使用存储的组件,而不是生成一个新组件。但是,如何操作 取决于您!
route.params
route.queryParams
重用策略
/** * 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方法即可!可能是这样的:
search/:term
www.yourwebsite.com/search/thingsearchedfor
view/:resultId
首先, 让我们创建一个要存储的路径数组。
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而不是所有其他组件的组件!