-
Notifications
You must be signed in to change notification settings - Fork 26.2k
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
Comments
I have also posted the issue on SO |
@vsavkin can you perhaps have a look at this issue? |
The issue seems to be with the reuse strategy, the default strategy works fine. |
@vicb The reuse strategy works fine on top level paths, is the reuse strategy not compatible with child paths? |
from the error message it seems that you are trying to reattach a route at the wrong place (level). |
@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. |
I too have same issue with routes containing child routes when using reuse strategy. |
This is router config from plunkr:
Custom reuse strategy uses The question is how should we choose key for routes when implementing custom reuse strategy? |
@vicb Any ideas on the above? |
Same issue here. Will there be a solution anytime soon? |
Same here. |
@jerrink me,too,me,too,I'm so sad can't to deal it. now you can run it correctly? |
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. |
Same issue here... |
@jitsmaster did you successfully work around the issue? |
Nope, had to drop sticky routes, due to time constraint.
…On Mon, Mar 13, 2017, 9:02 PM corbfon ***@***.***> wrote:
@jitsmaster <https://github.com/jitsmaster> did you successfully work
around the issue?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#13869 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/APGpoaDt1XthbMkkGu41ZriRHUGR6a2Iks5rlhFFgaJpZM4LgRrG>
.
|
don't use route pattern use route pattern resolved with params. |
My workaround for this problem was to make my retrieve function check for loadChildren on the routeConfig like so:
|
Same issue here. |
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. |
The AttachDetachReuse from the test case in the latest code: class AttachDetachReuseStrategy implements RouteReuseStrategy {
Still seems to be instantiating new components every-time on navigation. |
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. |
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:
then I make shouldDetach function also check for loadChildren to prevent RouteReuseStrategy save wrong ActivatedRouteSnapshot:
|
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? |
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:
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 |
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? |
@flymithra maybe you can ask @wardbell as he closed this issue with the following reason:
|
... so long that I am totally lost what is the core of that topic. |
我使用ng2 admin 1.0.0 angular 4+ , 想使用路由重用这个功能,已经使用各位前辈的方法,但是还是没有解决问题。 |
@AdolphYu Replace the content of function retrieve with the code below:
|
the following is work! reference:https://www.cnblogs.com/lovesangel/p/7853364.html import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router'; /**
export class CustomReuseStrategy implements RouteReuseStrategy {
} |
lol, why is this closed ? The linked issue when you closed this, has nothing to do with this. |
@pkozlowski-opensource can you please have a look at this issue or reopen it? |
Please reopen. It is a bug. |
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.
Here is the basic custom router link implementation:
Note that this still does not work if you have parent routes that do not have default child routes. |
@DanRibbens 的答案很具有参考价值,如果还有问题,可能是开启了热加载!hha |
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. |
@jodymcguire did it work with lazy Loading routes? |
Not to the degree that I was seeking. Lazy routes work at the parent window level, but as I created child pages in the parent, and then went to switch back and forth between the parent routes, the child routes would get confused as to which parent they belonged to, causing the wrong child pages to appear.
I experimented with many implementations of the custom route reuse strategy, but in the end kept lazy loading at the parent window level, and then use manual child component creation for the child pages, which is working very well. If interested, see https://hackernoon.com/angular-pro-tip-how-to-dynamically-create-components-in-body-ba200cc289e6.
From: Hadi Rasouli <notifications@github.com>
Sent: Monday, November 19, 2018 4:30 AM
To: angular/angular <angular@noreply.github.com>
Cc: Jody McGuire <Jody.McGuire@axispointhealth.com>; Mention <mention@noreply.github.com>
Subject: Re: [angular/angular] Cannot reattach ActivatedRouteSnapshot created from a different route (#13869)
@jodymcguire<https://github.com/jodymcguire> did it work with lazy Loading routes?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub<#13869 (comment)>, or mute the thread<https://github.com/notifications/unsubscribe-auth/AQoY2prC0u_dfTvi0YPr1VcuBENJ6H9Iks5uwpYkgaJpZM4LgRrG>.
|
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:
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 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:
This is not enough. Also using the full url including all parents Additions:
once you store a handle in your reuse strategy, angular is never destroying related Components. You need to call
When you store the handle based on a simple key 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
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 |
Will surely make some tests. Thanks @marcj |
I can't seem to get that to work @marcj , it triggers, but doesn't store things. |
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. |
Thank you @marcj. Your solution worked for me. It also worked with Angular 6.x |
2 years passed from I hit this error and still there is no solution for this problem. not good |
For all who looking for temporary workaround - you can disable Route Reuse just by addnig this in constructor:
|
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. |
hi marcj: |
you should replace the part of code : |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
I'm submitting a ... (check one with "x")
Current behavior
Expected behavior
Minimal reproduction of the problem with instructions
Step1

Step2

Step3

Step4

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
The text was updated successfully, but these errors were encountered: