我正在尝试实施RouteReuseStrategy
类。当我导航到顶级路径时,它工作正常。
一旦路径有子路径并导航到子路径,然后导航回顶级路径我收到以下错误:
错误:未捕获(在承诺中):错误:无法重新附加从其他路径创建的ActivatedRouteSnapshot
我创建了一个plunker来演示错误。我发现在IE 11中,plunker不起作用,请在最新版本的Chrome中查看
重现错误的步骤:
我已经尝试了在这个article上找到的实现
export class CustomReuseStrategy implements RouteReuseStrategy {
handlers: {[key: string]: DetachedRouteHandle} = {};
shouldDetach(route: ActivatedRouteSnapshot): boolean {
console.debug('CustomReuseStrategy:shouldDetach', route);
return true;
}
store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
console.debug('CustomReuseStrategy:store', route, handle);
this.handlers[route.routeConfig.path] = handle;
}
shouldAttach(route: ActivatedRouteSnapshot): boolean {
console.debug('CustomReuseStrategy:shouldAttach', route);
return !!route.routeConfig && !!this.handlers[route.routeConfig.path];
}
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
console.debug('CustomReuseStrategy:retrieve', route);
if (!route.routeConfig) return null;
return this.handlers[route.routeConfig.path];
}
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
console.debug('CustomReuseStrategy:shouldReuseRoute', future, curr);
return future.routeConfig === curr.routeConfig;
}
}
并执行此stackoverflow answer
/**
* 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 (===)
* @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;
}
}
RouteReuseStrategy
准备好孩子paths
?或者是否有另一种方法让RouteReuseStrategy
使用包含子paths
的路径
我添加了一个解决方法,通过修改自定义RouteReuseStrategy中的检索功能,在使用loadChildren的路径上永远不会检索分离的路由。
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
if (!route.routeConfig) return null;
if(route.routeConfig.loadChildren) return null;
return this.handlers[route.routeConfig.path];
}
我不确定它是所有场景的完美解决方案,但在我的情况下它是有效的。
这是一种为策略类中的路径生成唯一键的方法。我有类似的问题但是一旦我开始生成唯一键,问题就消失了:
private takeFullUrl(route: ActivatedRouteSnapshot) {
let next = route;
// Since navigation is usually relative
// we go down to find out the child to be shown.
while (next.firstChild) {
next = next.firstChild;
}
const segments = [];
// Then build a unique key-path by going to the root.
while (next) {
segments.push(next.url.join('/'));
next = next.parent;
}
return compact(segments.reverse()).join('/');
}
更多关于那https://github.com/angular/angular/issues/13869#issuecomment-344403045
角度路由器不必要地复杂,自定义策略继续这种趋势。
您的自定义策略使用route.routerConfig.path
作为存储路由的密钥。
它为相同的路径person/:id
存储(覆盖)两条不同的路由:
/person/%23123456789%23/edit
/person/%23123456789%23/view
第一次查看路径已存储,第二次编辑,当您再次打开视图时,最后存储的路径被编辑,但视图是预期的。
根据路由器的意见,这条路线是不兼容的,它递归地检查节点,并发现routerConfig
的ViewPersonComponent
与routerConfig
的EditPersonComponent
不同,繁荣!
所以要么routerConfig.path
不能用作密钥,要么是路由器设计问题/限制。
我遇到了类似的问题,修改我的唯一密钥方法解决了它。
private routeToUrl(route: ActivatedRouteSnapshot): string {
if (route.url) {
if (route.url.length) {
return route.url.join('/');
} else {
if (typeof route.component === 'function') {
return `[${route.component.name}]`;
} else if (typeof route.component === 'string') {
return `[${route.component}]`;
} else {
return `[null]`;
}
}
} else {
return '(null)';
}
}
private getChildRouteKeys(route:ActivatedRouteSnapshot): string {
let url = this.routeToUrl(route);
return route.children.reduce((fin, cr) => fin += this.getChildRouteKeys(cr), url);
}
private getRouteKey(route: ActivatedRouteSnapshot) {
let url = route.pathFromRoot.map(it => this.routeToUrl(it)).join('/') + '*';
url += route.children.map(cr => this.getChildRouteKeys(cr));
return url;
}
以前我只是建立了第一个孩子,现在我只是递归地建立了所有孩子的钥匙。我没有编写routeToUrl函数,我从一篇关于自定义重用策略的文章中得到了它,并且它是未经修改的。
在我的情况下,我需要在检索方法中检查route.routeConfig.children:
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
if (!route.routeConfig) return null;
if (route.routeConfig.loadChildren || route.routeConfig.children ) return null;
return this.handlers[route.routeConfig.path];
}