Skip to content

Cannot reattach ActivatedRouteSnapshot created from a different route #13869

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
tjaartvanderWalt opened this issue Jan 11, 2017 · 65 comments
Closed

Comments

@tjaartvanderWalt
Copy link

tjaartvanderWalt commented Jan 11, 2017

I'm submitting a ... (check one with "x")

[x] bug report => search github for a similar issue or PR before submitting
[ ] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior

Navigate to a route that has child routes.
Navigate to one of the child routes.
Navigate back to top level route.
Navigate to any other root. (this is where you receive the error)

Expected behavior

Navigate to a route that has child routes.
Navigate to one of the child routes.
Navigate back to top level route.
Navigate to any other root. (without receiving the error)

Minimal reproduction of the problem with instructions

Step1
1

Step2
2

Step3
3

Step4
4

Error
error

https://plnkr.co/edit/GuQuWnW2GsfnBOVyWQRh?p=preview

What is the motivation / use case for changing the behavior?
To fix the bug

Please tell us about your environment:
ASP.Net Core

  • Angular version: 3.1.1

  • Angular/router Version: 3.3.1

  • Browser: [Chrome]

  • Language: [TypeScript 1.0.3.0 | ES6 | ES5]

  • Node (for AoT issues): node v6.9.2

@tjaartvanderWalt
Copy link
Author

I have also posted the issue on SO

@tjaartvanderWalt
Copy link
Author

@vsavkin can you perhaps have a look at this issue?

@vicb
Copy link
Contributor

vicb commented Jan 13, 2017

The issue seems to be with the reuse strategy, the default strategy works fine.

@tjaartvanderWalt
Copy link
Author

@vicb The reuse strategy works fine on top level paths, is the reuse strategy not compatible with child paths?

@vicb
Copy link
Contributor

vicb commented Jan 13, 2017

from the error message it seems that you are trying to reattach a route at the wrong place (level).

@tjaartvanderWalt
Copy link
Author

tjaartvanderWalt commented Jan 13, 2017

@vicb I am not sure what you mean by "wrong place/level", can you please elaborate? Can you please run the above plunker? I have tried to specify the provider at component level instead of at the application wide level, but then the reuse strategy does not get invoked. Thanks.

@jerrink
Copy link

jerrink commented Jan 13, 2017

I too have same issue with routes containing child routes when using reuse strategy.
I get the error when I try to navigate back to a (stored) route with child routes.

@kemsky
Copy link

kemsky commented Jan 19, 2017

This is router config from plunkr:

    {path: '', redirectTo: 'search', pathMatch: 'full'},
    {path: 'search', component: SearchComponent},
    {
      path: 'person/:id', component: PersonComponent,
      children:[
        { path: '', redirectTo: 'view', pathMatch: 'full' },
        { path: 'view', component: ViewPersonComponent },
        { path: 'edit', component: EditPersonComponent }
        ]
    }

Custom reuse strategy uses route.routeConfig.path as a key in local storage, but it does not work when you have child routes, in this case you can see that there are two child routes for person/:id: View and Edit. Storage entry for person/:id gets overwritten with person/:id/Edit and when person/:id is retrieved last time it actually returns person/:id/Edit route, but expected route is person/:id/View.

The question is how should we choose key for routes when implementing custom reuse strategy? route.routeConfig.path is not suitable because of the reasons stated above, we need unique route id for any given route. Another question is why we get person/:id here at all? It is in the middle of the path.

@corbfon
Copy link

corbfon commented Feb 9, 2017

@vicb Any ideas on the above?

@jitsmaster
Copy link

Same issue here. Will there be a solution anytime soon?
Look like somewhere in the middle, the children router outlets got swapped. The first child router outlet is used for url meant for the 2nd outlet.

@jitsmaster
Copy link

Further debugging confirmed my finding. It was indeed the outlet mapped passed in getting mixed up

image

On the call of activateRoutes from ActivateRoutes class, the parentOutletMap passed in contains the wrong primary outlet in its _outlets property.

@smarty77
Copy link

Same here.
Any ideas or workarounds?

@lizardd
Copy link

lizardd commented Feb 27, 2017

@jerrink me,too,me,too,I'm so sad can't to deal it. now you can run it correctly?

@kemsky
Copy link

kemsky commented Feb 27, 2017

Workaround is to build full path using child route configs (unless you have many router outlets, don't know what to do in this case).

Overall it does not look right to me, it is clearly show stopper for reuse strategy.

@0xNacho
Copy link

0xNacho commented Mar 7, 2017

Same issue here...

@corbfon
Copy link

corbfon commented Mar 14, 2017

@jitsmaster did you successfully work around the issue?

@jitsmaster
Copy link

jitsmaster commented Mar 14, 2017 via email

@jorgeunimicro
Copy link

don't use route pattern use route pattern resolved with params.

@DanRibbens
Copy link

My workaround for this problem was to make my retrieve function check for loadChildren on the routeConfig like so:

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

@danzrou
Copy link

danzrou commented Apr 13, 2017

Same issue here.
Did anyone find some workaround ?

@vinaysoni
Copy link

vinaysoni commented Apr 16, 2017

Same issue here. The most natural is to default to routeReuse (sticky components - components should always be reused unless specifically destroyed) along with their nested routes as it covers 99% of the use cases.

If there is a change in the view/route1 due to actions on view/route2, an event listener can easily be used to modify the data in view/route1. A drastic decision such as to destroy components when routing away, is difficult to digest.

Destruction of components should be the special case and could require a custom RouteDestroyStrategy.

@vinaysoni
Copy link

vinaysoni commented Apr 17, 2017

The AttachDetachReuse from the test case in the latest code:

class AttachDetachReuseStrategy implements RouteReuseStrategy {
stored: {[k: string]: DetachedRouteHandle} = {};

  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    return route.routeConfig.path === 'a';
  }

  store(route: ActivatedRouteSnapshot, detachedTree: DetachedRouteHandle): void {
    this.stored[route.routeConfig.path] = detachedTree;
  }

  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    return !!this.stored[route.routeConfig.path];
  }

  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
    return this.stored[route.routeConfig.path];
  }

  shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    return future.routeConfig === curr.routeConfig;
  }
}

Still seems to be instantiating new components every-time on navigation.

https://github.com/angular/angular/blob/82417b3ca59aa2e5f41ee12db4cb18970b5b4f47/packages/router/test/integration.spec.ts

@vinaysoni
Copy link

Is it possible for the ng2 router team to publish a correct RouteReuseStrategy that would work properly for nested routes and avoid this defect? Currently this is not available anywhere - including SO or any other website.

Since this is a fundamental underpinning of how one would design their application (for those who want to avoid destructing the components), it is not possible to even start developing an Angular2 application without a correct (some kind of) AttachDetachReuseStrategy.

There seems to be no communication on this since Jan. Please help.

@LeeGenD
Copy link

LeeGenD commented Apr 26, 2017

Thanks for @DanRibbens 's workaround.Base on it,I find my workaround make my app's RouteReuseStrategy works fine.

As DanRibbens says,make retrieve function check for loadChildren:

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

then I make shouldDetach function also check for loadChildren to prevent RouteReuseStrategy save wrong ActivatedRouteSnapshot:

shouldDetach(route: ActivatedRouteSnapshot): boolean {
        if (!route.routeConfig || route.routeConfig.loadChildren) {
            return false;
        }
        return true;
    }

@ccrowhurstram
Copy link

ccrowhurstram commented May 13, 2017

Saw this commit in a sticky routes sample by @manfredsteyer where it looks like he was trying to solve the same problem - maybe this could be used for inspiration?

@ishor13
Copy link

ishor13 commented May 15, 2017

From what I can tell the main issue is that angular is internally comparing the entire detached tree for a given route and if ALL nested routes are not the same then the entire route fails.

In terms of the OP:

  • When you navigate back to the top level route (after editing a person), the stored route is the entire url tree starting at the 'person/:id' level. (essentially 'person/:id/edit')
  • When you navigate back to the 'person/:id' via a "view" button the new route is 'person/:id/view', which fails angular's internal check (setFutureSnapshotsOfActivatedRoutes) since the child routes for 'person/:id' are different

My solution is to update/override any redirects when I store a route

export class CustomRouteReuseStrategy extends RouteReuseStrategy {
  handlers: {[path:string]:DetachedRouteHandle} = {};

  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    //Avoid second call to getter
    let config: Route = route.routeConfig;
    //Don't store lazy loaded routes
    return config && !config.loadChildren;
  }

  store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
    let path: string = this.getRoutePath(route);
    this.handlers[path] = handle;
    /*
      This is where we circumvent the error.
      Detached route includes nested routes, which causes error when parent route does not include the same nested routes
      To prevent this, whenever a parent route is stored, we change/add a redirect route to the current child route
    */
    let config: Route = route.routeConfig;
    if(config) {
      let childRoute: ActivatedRouteSnapshot = route.firstChild;
      let futureRedirectTo = childRoute ? childRoute.url.map(function(urlSegment) {
        return urlSegment.path;
      }).join('/') : '';
      let childRouteConfigs: Route[] = config.children;
      if(childRouteConfigs) {
        let redirectConfigIndex: number;
        let redirectConfig: Route = childRouteConfigs.find(function(childRouteConfig, index) {
          if(childRouteConfig.path === '' && !!childRouteConfig.redirectTo) {
            redirectConfigIndex = index;
            return true;
          }
          return false;
        });
        //Redirect route exists
        if(redirectConfig) {
          if(futureRedirectTo !== '') {
            //Current activated route has child routes, update redirectTo
            redirectConfig.redirectTo = futureRedirectTo;
          } else {
            //Current activated route has no child routes, remove the redirect (otherwise retrieval will always fail for this route)
            childRouteConfigs.splice(redirectConfigIndex, 1);
          }
        } else if(futureRedirectTo !== '') {
          childRouteConfigs.push({
            path: '',
            redirectTo: futureRedirectTo,
            pathMatch: 'full'
          });
        }
      }
    }
  }

  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    return !!this.handlers[this.getRoutePath(route)];
  }

  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
    let config: Route = route.routeConfig;
    //We don't store lazy loaded routes, so don't even bother trying to retrieve them
    if(!config || config.loadChildren) {
      return false;
    }
    return this.handlers[this.getRoutePath(route)];
  }

  shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    return future.routeConfig === curr.routeConfig;
  }

  getRoutePath(route: ActivatedRouteSnapshot): string {
    let namedOutletCount: number = 0;
    return route.pathFromRoot.reduce((path, route) => {
      let config: Route = route.routeConfig;
      if(config) {
        if(config.outlet && config.outlet !== PRIMARY_OUTLET) {
          path += `(${config.outlet}:`;
          namedOutletCount++;
        } else {
          path += '/';
        }
        return path += config.path
      }
      return path;
    }, '') + (namedOutletCount ? new Array(namedOutletCount + 1).join(')') : '');
  }
}

I know this doesn't help everybody (or the OP for that matter), but it is sufficient for my use case and may be for some of you

Note: My actual RouteReuseStrategy is slightly more complicated (I extended the Route interface to provide additional configuration, allowing for each route to be handled differently).

@dmitrimaltsev
Copy link

Also, this seems to be related to #6634 and #20072

@flymithra
Copy link

Still don't know how to properly use it. I have nested routes and all time it says: "Cannot reattach ActivatedRouteSnapshot created from a different route". Has Angular another way to reuse routes?

@tjaartvanderWalt
Copy link
Author

@flymithra maybe you can ask @wardbell as he closed this issue with the following reason:

The problem is obscure and not of general interest.

@mlc-mlapis
Copy link
Contributor

mlc-mlapis commented Jan 29, 2018

... so long that I am totally lost what is the core of that topic.

@AdolphYu
Copy link

我使用ng2 admin 1.0.0 angular 4+ , 想使用路由重用这个功能,已经使用各位前辈的方法,但是还是没有解决问题。

@TDMaker
Copy link

TDMaker commented Feb 5, 2018

@AdolphYu Replace the content of function retrieve with the code below:

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

@5hongbing
Copy link

the following is work! reference:https://www.cnblogs.com/lovesangel/p/7853364.html

import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router';

/**

  • 主要思想:
  • angular 路由就是棵树, 当 url 从 a/b/c/d 切换到 a/e/f/g 时.
  • shouldReuseRoute 会被触发. angular 会让你去对比看是否要 reuse
  • a vs a
  • b vs e
  • c vs f
  • d vs g
  • 一般情况下 a vs a 自然是 reuse
  • b vs e 就替换, 而一旦 parent 被替换, 那么所有 child 自然也是被替换的.
  • NOTE!:Detach和Attach指的是是否从DOM树中脱离和加入DOM树。shouldDetach和shouldAttach的含义为onDeatch和onAttach,意味着在Detach和Attach时用户可以做出某种决定!
  • 替换一旦发生, 就会有某些组件要被丢弃 destroy, 这时 shouldDetech, store 就会被调用, 用于缓存这些已经渲染完成即将被丢弃的组件.
  • 有组件被丢弃自然有组件需要进来替补, 而这时 shouldAttach,retrieve 就会被调用, 用来调出缓存的组件.
  • 所以流程是这样 :
    1. 是否替换 ?
    1. 替换发生, 有组件离去, 有组件加入
    1. 离去的组件, 我们可以缓存
    1. 加入的组件, 我们可以使用缓存好的组件.
  • 替换->缓存->重用 就是整体的核心了.
    */

export class CustomReuseStrategy implements RouteReuseStrategy {

public static handlers: { [key: string]: DetachedRouteHandle } = {}

private static waitDelete: string

public static deleteRouteSnapshot(name: string): void {
    if (CustomReuseStrategy.handlers[name]) {
        delete CustomReuseStrategy.handlers[name];
    } else {
        CustomReuseStrategy.waitDelete = name;
    }
}
/** 表示对所有路由允许复用 如果你有路由不想利用可以在这加一些业务逻辑判断 */
public shouldDetach(route: ActivatedRouteSnapshot): boolean {
    return true;
}

/** 当路由离开时会触发。按path作为key存储路由快照&组件当前实例对象 */
public store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
    if (CustomReuseStrategy.waitDelete && CustomReuseStrategy.waitDelete == this.getRouteUrl(route)) {
        // 如果待删除是当前路由则不存储快照
        CustomReuseStrategy.waitDelete = null
        return;
    }
    CustomReuseStrategy.handlers[this.getRouteUrl(route)] = handle
}

/** 若 path 在缓存中有的都认为允许还原路由 */
public shouldAttach(route: ActivatedRouteSnapshot): boolean {
    return !!CustomReuseStrategy.handlers[this.getRouteUrl(route)]
}

/** 从缓存中获取快照,若无则返回nul */
public retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
    if (!route.routeConfig) {
        return null
    }

    return CustomReuseStrategy.handlers[this.getRouteUrl(route)]
}

/** 进入路由触发,判断是否同一路由 */
public shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    return future.routeConfig === curr.routeConfig &&
        JSON.stringify(future.params) === JSON.stringify(curr.params);
}

private getRouteUrl(route: ActivatedRouteSnapshot) {
    return route['_routerState'].url.replace(/\//g, '_')
}

}

@nickwinger
Copy link

nickwinger commented Mar 13, 2018

lol, why is this closed ?
This issue is still not resolved here or documented in the Angular-Docs.
There is no info how to securly use the ReuseStrategy and in more complex scenarios with lazy loaded child routes i get lost...

The linked issue when you closed this, has nothing to do with this.
It is still not an answer.
We need professional examples in the documentation on how to use the RouterReuseStrategy also in complex scenarios...

Sorry, something went wrong.

@tjaartvanderWalt
Copy link
Author

@pkozlowski-opensource can you please have a look at this issue or reopen it?

Sorry, something went wrong.

@kavi87
Copy link

kavi87 commented May 17, 2018

Please reopen. It is a bug.

Sorry, something went wrong.

@pcurrivan
Copy link

pcurrivan commented Jul 10, 2018

Thank you @dmitrimaltsev for the workaround. I made a demo of it (including a custom router link directive like you mentioned) here on stackblitz

I slightly modified the reuse strategy to update the redirects when a route is reused. Otherwise navigating e.g. Parent/Child1 -> Sibling -> Parent/Child1 -> Parent/Child2 -> Parent will result in a redirect to Parent/Child1.

  shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    let ret = future.routeConfig === curr.routeConfig;
    if (ret) {
      this.addRedirectsRecursively(future); // update redirects
    }
    return ret;
  }

Here is the basic custom router link implementation:

import { Directive, ElementRef, OnInit, Input } from '@angular/core'
import { Router } from '@angular/router'

@Directive({
  selector: '[customRouterLink]'
})
export class CustomRouterLink implements OnInit {

  @Input() customRouterLink: string = '';
  private pathParts: string[];

  constructor(
    private elementRef: ElementRef,
    private router: Router) {}

  ngOnInit() {
    this.pathParts = this.customRouterLink.split("/");
    this.elementRef.nativeElement.addEventListener('click', this.activate.bind(this));
  }

  activate() {
    if (this.pathParts.length) {
      let p = this.pathParts[0];
      // console.log("navigating to " + p);
      let promise: Promise<any> = this.router.navigateByUrl(p);

      for (let i = 1; i < this.pathParts.length; i++) {
        p = p + "/" + this.pathParts[i];
        promise = promise.then(() => {
          // console.log("navigating to " + p);
          this.router.navigateByUrl(p);
        });
      }        
    }
  }
}

Note that this still does not work if you have parent routes that do not have default child routes.

@liuganchen
Copy link

@DanRibbens 的答案很具有参考价值,如果还有问题,可能是开启了热加载!hha

@jodymcguire
Copy link

I concur with @gnovotny. @dmitrimaltsev this is amazing and with some minor tweaks to accommodate the translation of spaces in the path values with the percent 20 replacement I was able to use your solution. Additional kudos to @pcurrivan for his stackblitz project as from there I was able to finish off my solution with the notations for reuse on the route paths.

@hadirsa
Copy link

hadirsa commented Nov 19, 2018

@jodymcguire did it work with lazy Loading routes?

@jodymcguire
Copy link

jodymcguire commented Nov 19, 2018 via email

@marcj
Copy link

marcj commented Nov 22, 2018

Alright, guys, here's the solution:

@wardbell is absolutely right, the problem is obscure and you need to think about it in more detail.

Let's assume we have following routes:

    {path: 'settings', component: SettingsComponent, children: []},
    {path: 'project', component: ProjectListComponent, children: [
        {path: ':id', component: ProjectComponent},
    ]},
  • You open /project/1. So you tell Angular to load a route which contains 2 routes (the actual one and one children).
  • Then open /settings. What happens now is interesting:
    • Angular triggers RouteReuseStrategy::store for route project, which contains the very moment one children in its handle. So you save that including the children (as you can't and shouldn't modify the handle)
  • You go back to /project (note: not the one from 1.). Now Angular wants from your ReuseStrategy the handle for project. You return the one with 1 children (since you have stored that, remember?)
    • Angular wants now to load a view containing only one route with a handle that contains two routes. What to do? Well, angular decides to throw an error, which is fine to me, as the behavior is not very obvious. Should angular instead remove that children route and destroy its Component? I don't know. Why don't you return a correct one?

So what's the issue here? The issue is that you return a handle that is not the correct one. You return a handle containing a children although angular wanted the handle for /project (no children involved here).

The solution is simple: Store the handle not based on route name, not even on url, but on the whole state with children. Usually you store it like this:

store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle | null): void {
   this.myStore[route.routeConfig.path] = handle;
}

This is not enough. Also using the full url including all parents path is not enough. You need a different key to define the current state of ActivatedRouteSnapshot, which is perfectly defined by its full url PLUS its childrens.

Additions:

  1. Memory Leak

once you store a handle in your reuse strategy, angular is never destroying related Components. You need to call destroy() on them once you remove them out of your cache. However, since DetachedRouteHandle is defined as {} you need currently a little hack.

  1. Multiple caches per resolved url

When you store the handle based on a simple key path of your config or the full path including all parents, then you store for dynamic routes (like /project/:id) only ever the latest. My following implementation allows you to define for example to hold the last 5 detail views of /project/:id.

Here is an implementation that should cover all use-cases:

import {ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy} from "@angular/router";
import {ComponentRef} from "@angular/core";

interface RouteStates {
    max: number;
    handles: {[handleKey: string]: DetachedRouteHandle};
    handleKeys: string[];
}

function getResolvedUrl(route: ActivatedRouteSnapshot): string {
    return route.pathFromRoot
        .map(v => v.url.map(segment => segment.toString()).join('/'))
        .join('/');
}

function getConfiguredUrl(route: ActivatedRouteSnapshot): string {
    return '/' + route.pathFromRoot
        .filter(v => v.routeConfig)
        .map(v => v.routeConfig!.path)
        .join('/');
}

export class ReuseStrategy implements RouteReuseStrategy {
    private routes: {[routePath: string]: RouteStates } = {
        '/project': {max: 1, handles: {}, handleKeys: []},
        '/project/:id': {max: 5, handles: {}, handleKeys: []}
    };

    /** Determines if this route (and its subtree) should be detached to be reused later */
    shouldDetach(route: ActivatedRouteSnapshot): boolean {
        return !!this.routes[getConfiguredUrl(route)];
    }

    private getStoreKey(route: ActivatedRouteSnapshot) {
        const baseUrl = getResolvedUrl(route);

        //this works, as ActivatedRouteSnapshot has only every one children ActivatedRouteSnapshot
        //as you can't have more since urls like `/project/1,2` where you'd want to display 1 and 2 project at the
        //same time
        const childrenParts = [];
        let deepestChild = route;
        while (deepestChild.firstChild) {
            deepestChild = deepestChild.firstChild;
            childrenParts.push(deepestChild.url.join('/'));
        }

        //it's important to separate baseUrl with childrenParts so we don't have collisions.
        return baseUrl + '////' + childrenParts.join('/');
    }

    /**
     * Stores the detached route.
     *
     * Storing a `null` value should erase the previously stored value.
     */
    store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle | null): void {
        if (route.routeConfig) {
            const config = this.routes[getConfiguredUrl(route)];
            if (config) {
                const storeKey = this.getStoreKey(route);
                if (handle) {
                    if (!config.handles[storeKey]) {
                        //add new handle
                        if (config.handleKeys.length >= config.max) {
                            const oldestUrl = config.handleKeys[0];
                            config.handleKeys.splice(0, 1);

                            //this is important to work around memory leaks, as Angular will never destroy the Component
                            //on its own once it got stored in our router strategy.
                            const oldHandle = config.handles[oldestUrl] as { componentRef: ComponentRef<any> };
                            oldHandle.componentRef.destroy();

                            delete config.handles[oldestUrl];
                        }
                        config.handles[storeKey] = handle;
                        config.handleKeys.push(storeKey);
                    }
                } else {
                    //we do not delete old handles on request, as we define when the handle dies
                }
            }
        }
    }

    /** Determines if this route (and its subtree) should be reattached */
    shouldAttach(route: ActivatedRouteSnapshot): boolean {
        if (route.routeConfig) {
            const config = this.routes[getConfiguredUrl(route)];

            if (config) {
                const storeKey = this.getStoreKey(route);
                return !!config.handles[storeKey];
            }
        }

        return false;
    }

    /** Retrieves the previously stored route */
    retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
        if (route.routeConfig) {
            const config = this.routes[getConfiguredUrl(route)];

            if (config) {
                const storeKey = this.getStoreKey(route);
                return config.handles[storeKey];
            }
        }

        return null;
    }

    /** Determines if `curr` route should be reused */
    shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
        return getResolvedUrl(future) === getResolvedUrl(curr) && future.routeConfig === curr.routeConfig;
    }
}

Note: To destroy elements we used above

const oldHandle = config.handles[oldestUrl] as { componentRef: ComponentRef<any> };
oldHandle.componentRef.destroy()

which can obviously break at any time Angular going forward with new releases. So take care of that when you upgrade. When Angular decides to build a contract on DetachedRouteHandle then we can drop that hack.

@danzrou
Copy link

danzrou commented Nov 22, 2018

Will surely make some tests. Thanks @marcj

@Troyd-Destin
Copy link

I can't seem to get that to work @marcj , it triggers, but doesn't store things.

@marcj
Copy link

marcj commented Dec 3, 2018

Maybe it's important: I use Angular v7. Feel free to adjust the code, so it works for you. If you debug further and give me more information maybe I can help you.

@jbrychka
Copy link

Thank you @marcj.

Your solution worked for me. It also worked with Angular 6.x

@sadra74
Copy link

sadra74 commented Mar 12, 2019

2 years passed from I hit this error and still there is no solution for this problem. not good

@kiryl-rumiacew
Copy link

kiryl-rumiacew commented Apr 12, 2019

For all who looking for temporary workaround - you can disable Route Reuse just by addnig this in constructor:

import {Router} from "@angular/router";

this.router.routeReuseStrategy.shouldReuseRoute = function(){ return false; };

@DanRibbens
Copy link

I'm going to make an analagy here.

Thank you for ordering your new vehicle with both anti-lock brakes and four wheel drive options. Unfortunately because you have selected both features, you'll have to disable ABS while your vehicle is in four wheel drive mode because they are incompatible.

I'd argue that it is better to stop pretending to offer routeReuse if it isn't compatible with lazy loading modules.

@sedll7809
Copy link

hi marcj:
my router like this
father A-> child A1
child A2
child A3
father B-> child B1
child B2
child B3
I use your code ,click link is not error,Jump is normal,But I can't cache the page, so when I debug it,
I find this place const config = this.routes[getConfiguredUrl(route)] ,config is undefined。my value
this.routes[‘/clues/list’].
Please help me check if there is any problem. I did not modify your code, I used it directly,thanks.

@fingercoder
Copy link

hi marcj:
my router like this
father A-> child A1
child A2
child A3
father B-> child B1
child B2
child B3
I use your code ,click link is not error,Jump is normal,But I can't cache the page, so when I debug it,
I find this place const config = this.routes[getConfiguredUrl(route)] ,config is undefined。my value
this.routes[‘/clues/list’].
Please help me check if there is any problem. I did not modify your code, I used it directly,thanks.

you should replace the part of code :
private routes: {[routePath: string]: RouteStates } = {
'/project': {max: 1, handles: {}, handleKeys: []},
'/project/:id': {max: 5, handles: {}, handleKeys: []}
};
Register your own code!
like this:
private routes: {[routePath: string]: RouteStates } = {
'/app_cim//bulletin/': {max: 5, handles: {}, handleKeys: []},
'/app_cim//bank//material-receive/': {max: 5, handles: {}, handleKeys: []}
};

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Oct 13, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests