Angular 错误:无法重新附加从其他路由创建的ActivatedRouteSnapshot

Angular 错误:无法重新附加从其他路由创建的ActivatedRouteSnapshot,angular,typescript,angular2-routing,Angular,Typescript,Angular2 Routing,我正在尝试实现路由使用策略类。当我导航到顶级路径时,它工作正常 一旦路径具有子路径,并且我导航到子路径,然后导航回顶层路径,我将收到以下错误: 错误:未捕获(承诺中):错误:无法重新附加从其他路由创建的ActivatedRouteSnapshot 我创建了一个示例来演示错误。我发现plunker在IE 11中不起作用,请在最新版本的Chrome中查看它 重现错误的步骤: 步骤1: 步骤2 步骤3 步骤4 您可以在控制台中查看错误: 我已经尝试了在这个平台上找到的实现 以及这个stack

我正在尝试实现
路由使用策略
类。当我导航到顶级路径时,它工作正常

一旦路径具有子路径,并且我导航到子路径,然后导航回顶层路径,我将收到以下错误:

错误:未捕获(承诺中):错误:无法重新附加从其他路由创建的ActivatedRouteSnapshot

我创建了一个示例来演示错误。我发现plunker在IE 11中不起作用,请在最新版本的Chrome中查看它

重现错误的步骤:

步骤1:

步骤2

步骤3

步骤4

您可以在控制台中查看错误:

我已经尝试了在这个平台上找到的实现

以及这个stackoverflow的实现


RouteReuseStrategy
是否为子路径
做好准备?或者有没有另一种方法来获取
路由使用策略
使用包含子
路径的路径

角度路由器不必要地复杂,自定义策略继续了这一趋势

您的自定义策略使用
route.routerConfig.path
作为存储路由的密钥

它为同一路径存储(覆盖)两条不同的路由
person/:id

  • /person/%23123456789%23/编辑
  • /person/%23123456789%23/查看
  • 第一次存储视图管线,第二次编辑,当您再次打开视图时,最后一次存储的管线是“编辑”,但需要查看

    根据路由器的意见,此路由不兼容,它递归检查节点,发现
    ViewPersonComponent
    routerConfig
    EditPersonComponent
    routerConfig
    不同,轰


    因此,
    routerConfig.path
    不能用作密钥,或者这是路由器设计问题/限制。

    我添加了一个解决方法,通过修改自定义RouterUseStrategy中的检索函数,在带有loadChildren的路由上,永远不会检索分离的路由

        retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
           if (!route.routeConfig) return null;
           if(route.routeConfig.loadChildren) return null;
           return this.handlers[route.routeConfig.path];
        }
    

    我不确定它是否是适用于所有场景的完美解决方案,但在我的情况下,它是有效的。

    以下是一种为strategy类中的routes生成唯一密钥的方法。 我也有类似的问题,但一旦我开始生成唯一密钥,问题就消失了:

    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('/');
    }
    

    关于这一点,我遇到了一个类似的问题,修改我的唯一键方法解决了这个问题

    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];
    }
    

    我刚刚在我的应用程序中解决了这个问题,其中包括延迟加载的模块。最后,我不得不解决一个路由重用策略,该策略将路由组件保留在模块内,而不是模块之间

    import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router';
    
    export class CustomReuseStrategy implements RouteReuseStrategy {
    
      handlers: { [key: string]: DetachedRouteHandle } = {};
    
      calcKey(route: ActivatedRouteSnapshot) {
        return route.pathFromRoot
          .map(v => v.url.map(segment => segment.toString()).join('/'))
          .filter(url => !!url)
          .join('/');
      }
    
      shouldDetach(route: ActivatedRouteSnapshot): boolean {
        return true;
      }
    
      store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
        this.handlers[this.calcKey(route)] = handle;
      }
    
      shouldAttach(route: ActivatedRouteSnapshot): boolean {
        return !!route.routeConfig && !!this.handlers[this.calcKey(route)];
      }
    
      retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
        if (!route.routeConfig) { return null as any; }
        if (route.routeConfig.loadChildren) {
          Object.keys(this.handlers).forEach(key => delete this.handlers[key]);
          return null as any;
        }
        return this.handlers[this.calcKey(route)];
      }
    
      shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
        return this.calcKey(curr) === this.calcKey(future);
      }
    
    }
    

    如果同一个问题尝试了不同的解决方案,这对我来说是有效的:

    import { RouteReuseStrategy} from "@angular/router/";
    import { ActivatedRouteSnapshot, DetachedRouteHandle } from "@angular/router";
    
    interface RouteStorageObject {
      snapshot: ActivatedRouteSnapshot;
      handle: DetachedRouteHandle;
    }
    
    export class CacheRouteReuseStrategy implements RouteReuseStrategy {
      storedRouteHandles = new Map<string, DetachedRouteHandle>();
      allowRetriveCache = {};
      storedRoutes: { [key: string]: RouteStorageObject } = {};
    
      shouldReuseRoute( before: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot):boolean {
       return before.routeConfig === curr.routeConfig;
      }
    
      retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
        if (!route.routeConfig || !this.storedRoutes[this.getPath(route)] ) return null as any;
        if (route.routeConfig.loadChildren) {
           Object.keys(this.storedRoutes).forEach(key => delete this.storedRoutes[key]);
           return null as any;
        }
        return this.storedRoutes[this.getPath(route)].handle;
      }
    
      shouldAttach(route: ActivatedRouteSnapshot): boolean {
        let canAttach: boolean = !!route.routeConfig && !!this.storedRoutes[this.getPath(route)];
        if (canAttach) {
           let paramsMatch: boolean = this.compareObjects(route.params, this.storedRoutes[this.getPath(route)].snapshot.params);
           let queryParamsMatch: boolean = this.compareObjects(route.queryParams, this.storedRoutes[this.getPath(route)].snapshot.queryParams);
    
           return paramsMatch && queryParamsMatch;
        } else {
           return false;
        }
      }
    
      shouldDetach(route: ActivatedRouteSnapshot): boolean {
        return true
      }
    
      store(route: ActivatedRouteSnapshot, detachedTree: DetachedRouteHandle): void {
         let storedRoute: RouteStorageObject = {
            snapshot: route,
            handle: detachedTree
         };
         if ( detachedTree != null ){
          this.storedRoutes[this.getPath(route)] = storedRoute;
         }
      }
    
      private getPath(route: ActivatedRouteSnapshot): string {
          return route.pathFromRoot
           .map(v => v.url.map(segment => segment.toString()).join('/'))
           .filter(url => !!url)
           .join('/');
      }
    
       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;
       }
    }
    
    从“@angular/router/”导入{RouteReuseStrategy};
    从“@angular/router”导入{ActivatedRouteSnapshot,DetachedRouteHandle};
    接口路由器对象{
    快照:ActivatedRouteSnapshot;
    手柄:可拆卸的手柄;
    }
    导出类CacheRouterUseStrategy实现RouterUseStrategy{
    StoredLoteHandles=新映射();
    AllowRetrieveCache={};
    StoreDrootes:{[key:string]:RouteStorageObject}={};
    shouldReuseRoute(在:ActivatedRouteSnapshot之前,当前:ActivatedRouteSnapshot):布尔值{
    在.routeConfig==curr.routeConfig之前返回;
    }
    检索(路由:ActivatedRouteSnapshot):DetachedRouteHandle | null{
    如果(!route.routeConfig | | |!this.storedrootes[this.getPath(route)])返回null作为任意值;
    if(route.routeConfig.loadChildren){
    Object.keys(this.storedrootes).forEach(key=>delete this.storedrootes[key]);
    返回空值,如有;
    }
    返回此.storedrotes[this.getPath(route)].handle;
    }
    shouldAttach(路由:ActivatedRouteSnapshot):布尔值{
    让canAttach:boolean=!!route.routeConfig&&!!this.storedrootes[this.getPath(route)];
    如果(鸭翼){
    让paramsMatch:boolean=this.compareObject(route.params,this.storedrootes[this.getPath(route)].snapshot.params);
    让queryParamsMatch:boolean=this.compareObject(route.queryParams,this.storedrootes[this.getPath(route)].snapshot.queryParams);
    返回paramsMatch&&queryParamsMatch;
    }否则{
    返回false;
    }
    }
    shouldDetach(路由:ActivatedRouteSnapshot):布尔值{
    返回真值
    }
    存储(路由:ActivatedRouteSnapshot,detachedTree:DetachedRouteHandle):无效{
    让StoreDroote:RouteStorageObject={
    快照:路线,
    把手:分离树
    };
    if(detachedTree!=null){
    this.storedrootes[this.getPath(route)]=storedroote;
    }
    }
    私有getPath(路由:ActivatedRouteSnapshot):字符串{
    return route.pathFromRoot
    .map(v=>v.url.map(segment=>segment.toString()).join('/'))
    .filter(url=>!!url)
    。加入(“/”);
    }
    私有比较对象(基:任意,比较:任意):布尔{
    //循环浏览基础对象中的所有属性
    for(让baseProperty位于base中){
    //确定Comparison对象是否具有该属性,如果没有:返回false
    if(比较.hasOwnProperty(baseProperty)){
    开关(基本类型[baseProperty]){
    //如果一个是对象,另一个不是:返回false
    //如果它们都是对象,则递归调用此比较函数
    案例“对象”:
    if(typeof compare[baseProperty]!='object'| |!this.compareObject(base[baseProperty],compare[baseProperty]){return false;}break;
    //如果一个是功能,另一个不是:
    
    import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router';
    
    export class CustomReuseStrategy implements RouteReuseStrategy {
    
      handlers: { [key: string]: DetachedRouteHandle } = {};
    
      calcKey(route: ActivatedRouteSnapshot) {
        return route.pathFromRoot
          .map(v => v.url.map(segment => segment.toString()).join('/'))
          .filter(url => !!url)
          .join('/');
      }
    
      shouldDetach(route: ActivatedRouteSnapshot): boolean {
        return true;
      }
    
      store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
        this.handlers[this.calcKey(route)] = handle;
      }
    
      shouldAttach(route: ActivatedRouteSnapshot): boolean {
        return !!route.routeConfig && !!this.handlers[this.calcKey(route)];
      }
    
      retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
        if (!route.routeConfig) { return null as any; }
        if (route.routeConfig.loadChildren) {
          Object.keys(this.handlers).forEach(key => delete this.handlers[key]);
          return null as any;
        }
        return this.handlers[this.calcKey(route)];
      }
    
      shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
        return this.calcKey(curr) === this.calcKey(future);
      }
    
    }
    
    import { RouteReuseStrategy} from "@angular/router/";
    import { ActivatedRouteSnapshot, DetachedRouteHandle } from "@angular/router";
    
    interface RouteStorageObject {
      snapshot: ActivatedRouteSnapshot;
      handle: DetachedRouteHandle;
    }
    
    export class CacheRouteReuseStrategy implements RouteReuseStrategy {
      storedRouteHandles = new Map<string, DetachedRouteHandle>();
      allowRetriveCache = {};
      storedRoutes: { [key: string]: RouteStorageObject } = {};
    
      shouldReuseRoute( before: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot):boolean {
       return before.routeConfig === curr.routeConfig;
      }
    
      retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
        if (!route.routeConfig || !this.storedRoutes[this.getPath(route)] ) return null as any;
        if (route.routeConfig.loadChildren) {
           Object.keys(this.storedRoutes).forEach(key => delete this.storedRoutes[key]);
           return null as any;
        }
        return this.storedRoutes[this.getPath(route)].handle;
      }
    
      shouldAttach(route: ActivatedRouteSnapshot): boolean {
        let canAttach: boolean = !!route.routeConfig && !!this.storedRoutes[this.getPath(route)];
        if (canAttach) {
           let paramsMatch: boolean = this.compareObjects(route.params, this.storedRoutes[this.getPath(route)].snapshot.params);
           let queryParamsMatch: boolean = this.compareObjects(route.queryParams, this.storedRoutes[this.getPath(route)].snapshot.queryParams);
    
           return paramsMatch && queryParamsMatch;
        } else {
           return false;
        }
      }
    
      shouldDetach(route: ActivatedRouteSnapshot): boolean {
        return true
      }
    
      store(route: ActivatedRouteSnapshot, detachedTree: DetachedRouteHandle): void {
         let storedRoute: RouteStorageObject = {
            snapshot: route,
            handle: detachedTree
         };
         if ( detachedTree != null ){
          this.storedRoutes[this.getPath(route)] = storedRoute;
         }
      }
    
      private getPath(route: ActivatedRouteSnapshot): string {
          return route.pathFromRoot
           .map(v => v.url.map(segment => segment.toString()).join('/'))
           .filter(url => !!url)
           .join('/');
      }
    
       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;
       }
    }