angular: Cannot reattach ActivatedRouteSnapshot created from a different route
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
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
About this issue
- Original URL
- State: closed
- Created 7 years ago
- Reactions: 31
- Comments: 65 (6 by maintainers)
Removing “docs” label because the solution is not something we intend to put in Angular docs. The problem is obscure and not of general interest. This kind of thing would be better in Stack Overflow.
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.
@wardbell please explain how the “problem is obscure and not of general interest”?
I don’t understand why this is an obscure requirement. If you have a list and a detail/view page you probably want to preserve search filters, sort, collapsible options, checkboxes, pagination etc when you go back.
So how to preserve state ?
Oner solution is to do it manually, save everything in a service, localstorage, cookie, query params whatever but that is sooo messy… saving so many state and setting them back is hard to maintain. Any new addition will require taking care of this new state. Not good.
Another solution is to have a
router-outlet
inside a parent component which holds the state. Not a big fan of this because states that should be the concerns of a child component are now moved up and the state still goes if you route outside of the parent component route because then the parent get destroyed. Not good.I’m wondering if something like class-transformer can make it easier to serialize component and restore them. But still, storing an entire object and rebuilding it just after a navigation ?
RouteReuseStrategy: this is so clean ! But it throws this error and I cannot find many resources on how to properly implement it.
So the initial question of how to preserve state is still open for me.
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…
Please reopen. It is a bug.
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:
setFutureSnapshotsOfActivatedRoutes
) since the child routes for ‘person/:id’ are differentMy solution is to update/override any redirects when I store a route
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 theRoute
interface to provide additional configuration, allowing for each route to be handled differently).@pkozlowski-opensource can you please have a look at this issue or reopen it?
My workaround for this problem was to make my retrieve function check for loadChildren on the routeConfig like so:
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:
/project/1
. So you tell Angular to load a route which contains 2 routes (the actual one and one children)./settings
. What happens now is interesting:RouteReuseStrategy::store
for routeproject
, which contains the very moment one children in itshandle
. So you save that including the children (as you can’t and shouldn’t modify thehandle
)/project
(note: not the one from 1.). Now Angular wants from your ReuseStrategy the handle forproject
. You return the one with 1 children (since you have stored that, remember?)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:
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 ofActivatedRouteSnapshot
, which is perfectly defined by its full url PLUS its childrens.Additions:
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, sinceDetachedRouteHandle
is defined as{}
you need currently a little hack.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:
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
DetachedRouteHandle
then we can drop that hack.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:
This is router config from plunkr:
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 forperson/:id
: View and Edit. Storage entry forperson/:id
gets overwritten withperson/:id/Edit
and whenperson/:id
is retrieved last time it actually returnsperson/:id/Edit
route, but expected route isperson/: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 getperson/:id
here at all? It is in the middle of the path.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.
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.
for nested route I just ignored the loadChildren setting to prevent errors.
and then I joined all the urls from pathFromRoot to prevent duplicate keys, we might use same key for the leaf route after all, I use a lot of ‘list’ in my project
further more, not all the routes need buffering states, for example, the best behavior for detail page is to init it each time. so I put a configuration in my routeConfig.data named ‘reuse’, so I will determine which page is buffered by route setting.
here’s my code: ` export class ConfigurableRouteReuseStrategy implements RouteReuseStrategy { handlers: { [key: string]: DetachedRouteHandle } = {};
} `
This still seems to be a problem with the router even if the docs team doesn’t want to add the details of a workaround to the angular docs site?
I agree with @christianacca in that it would be nice to hear from the Angular team on their plans for the experimental
RouteReuseStrategy
. Many people are trying to make use of it to solve user pain points in their app. Are there plans to move this API to stable? Improve it? Or deprecate it?I have also posted the issue on SO
I faced this problem, too. I modified @ishor13’s approach by adding redirects recursively to child routes.
The only downside is that when you navigate to deeply nested routes like this
/foo/fooChild -> /bar/barChild
, the router still fails, but I solved this with a custom version ofrouterLink
which in this case would first navigate to/bar
and then to/bar/barChild
, which works fine.I had similar problems until I realized that the main issue was what keys I used to store detached route handles. It’s very often that we navigate to children relative to the parent in angular apps. As a result what we get in the methods of
RouteReuseStrategy
asActivatedRouteSnapshot
is not a final route but some parent one. Usingroute.routeConfig.path
as a key is certainly not enough - it’ll be overriding values every time in the storage which causes Angular to curseCannot reattach
. When I changed key generation to as follows the problem disappeared:Hope will be useful for someone.
More information from my investigations…
It seems that the reuse strategy in the sample app sticky routes sample works in cases where your app has child routes. It does not seem to work when your app has sibling / auxiliary routes.
In this case the error that you run into is:
@flymithra maybe you can ask @wardbell as he closed this issue with the following reason:
10 months later and still no solution. Unreal.
don’t use route pattern use route pattern resolved with params.
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.
2 years passed from I hit this error and still there is no solution for this problem. not good
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 {
}
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; };
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.
Also, this seems to be related to https://github.com/angular/angular/issues/6634 and https://github.com/angular/angular/issues/20072
@dmitrimaltsev this is amazing, thanks
I had the same idea as @barbatus did. Here’s a smaller version doing the same thing.
Still doesn’t work for routes with children, so I had to flatten my route config a bit.
Following on from @wardbell comment, I guess a further question now comes up:
Is
RouteReuseStrategy
going to be moved forward ie moved to stable, and common use cases for it’s use in an app that uses child and auxiliary routes documented?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?
Further debugging confirmed my finding. It was indeed the outlet mapped passed in getting mixed up
On the call of activateRoutes from ActivateRoutes class, the parentOutletMap passed in contains the wrong primary outlet in its _outlets property.
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.