Skip to content
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

Router does not reuse component when switching between different Routes that use the same component. #12446

Closed
jamesmfriedman opened this issue Oct 21, 2016 · 42 comments

Comments

@jamesmfriedman
Copy link

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

2 or more route declarations point to the same component. If a different route matches to the same component, the DOM and component are thrown away.

/hero/1 -> /hero/2 no issue, component stays around
/hero/1 -> /hero/1/powers component is disposed of

Expected behavior

I should be able to change my route to anything and as long as it results in the same component being in the same routerOutlet, it should stay around. This is in reference to some comments in #9811

Minimal reproduction of the problem with instructions

In this example, my component should never be discarded and re-inited
https://plnkr.co/edit/T7K4TfcTdHoqJ7HaFByS?p=preview

What is the motivation / use case for changing the behavior?

in the above example, powers could be a tab name I want to cue off of. It could be something I ngIf on, or any other number of things.

Please tell us about your environment:

MacOS, Sublimet Text, Npm

  • Angular version: 2.0.X

    2.1.1

  • Browser: [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ]

    all

  • Language: [all | TypeScript X.X | ES6/7 | ES5]
    all

  • Node (for AoT issues): node --version =
    6.5.0

@DzmitryShylovich
Copy link
Contributor

The docs make is sound like the expected behavior is that is reuses the component, and the component can respond to the parameter changes.

This is exactly what is going on. See example I'm logging router params https://plnkr.co/edit/iuGO8pj6DEGFkDeJMylg?p=preview
For more info see https://angular.io/docs/ts/latest/guide/router.html#!#reuse

@jamesmfriedman
Copy link
Author

jamesmfriedman commented Oct 21, 2016

Expanded on your example. Notice the first time you click a person, it does in fact re-init the component.

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

contacts/group -> contacts/group/1 -> disposes of component
contacts/group/1 -> contacts/group/2 -> reuses component

@DzmitryShylovich
Copy link
Contributor

Oh, I see. Probably reuse mechanism can be improved.

@jamesmfriedman
Copy link
Author

I haven't dug into the source yet, so I'm not sure what rules are setup for reuse. I think this is a simple rule that would work:

When a route changes, if the resulting component is already present in the router-outlet, don't re-render.

Currently, it would seem that the rule is only applicable when you're on the exact same matching route.

@DzmitryShylovich
Copy link
Contributor

Pls update the issue title so it will be more clear what the issue is.

@jamesmfriedman jamesmfriedman changed the title Router discards component when exact path not matched. Router does not reuse component when switching between different Routes that use the same component. Oct 21, 2016
@tdhsmith
Copy link

tdhsmith commented Oct 24, 2016

Well in your example you're still navigating between routes (i.e. you've triggered a different node in your route configuration tree). It just happens that the route renders the same component in the same outlet. The parameter change stuff is irrelevant because it's a different node.

I think what the docs aren't saying (but are implying) is that you only get component reuse when the navigation triggers the exact same configuration node..?

That said, I'm not a team member -- I have no idea what the ultimate intent is here.

@zoechi
Copy link
Contributor

zoechi commented Oct 31, 2016

See also #7757 (comment)

@vsavkin
Copy link
Contributor

vsavkin commented Oct 31, 2016

Some background:

Currently the router reuses activated routes and not components. An activated route is associated with an item in the router configuration.

Since an activated route is injected in the component's constructor, we cannot reuse the same component instance when switching activated routes, at least not without introducing new primitives.

We are planning to introduce custom route reuse strategies. The following issue talks about it: #7757.

@vsavkin vsavkin closed this as completed Oct 31, 2016
@vinaysoni
Copy link

"Since an activated route is injected in the component's constructor, we cannot reuse the same component instance when switching activated routes, at least not without introducing new primitives."

  1. Activated route reuse is a behind the scene thing.
  2. Reuse of component, impacts the whole design of the app.
  3. If not the constructor, why not just use plain Interface and invoke the method on the component, to provide it with activated route? For such a simple thing, it is asking for too much to alter the application design paradigms that are fundamental.

@jamesmfriedman
Copy link
Author

jamesmfriedman commented Nov 11, 2016

Just a thought, the activated route could be an observable, like RouteParams, so you could still inject it, but you can watch it in case it changes.

Agreed on point number 2 @vinaysoni. It does impact the whole design of the app, which is why I brought it up :). From what I've read here, I'll change how I was planning on building the app, or move over to the new Ui Router.

@vinaysoni
Copy link

vinaysoni commented Nov 11, 2016

@jamesmfriedman this is a very serious issue, which the router team is mistaking for a minor one. Can't believe that they have gone so complex that they can't provide a simple interface and invoke it with the active route. Any experienced single page app developer will be revolted to see the components being destroyed like this. The app is creating and destroying components all the time, as you navigate - for no obvious reason (because Framework demands that). Really funny!!! This is amateurish decision.

"Just a thought, the activate route could be an observable, like RouteParams, so you could still inject it, but you can watch it in case it changes." @jamesmfriedman This is a great suggestion.

Thanks for mentioning UI Router. If push comes to shove, I am glad that there is UI-Router project.

@zoechi
Copy link
Contributor

zoechi commented Nov 11, 2016

there are easy workarounds for most use cases, therefore no need to make it a big deal

@vinaysoni
Copy link

vinaysoni commented Nov 11, 2016

Sorry Zoechi. I strongly disagree.
Workarounds for what? To keep destroying components again and again, for the work in progress (which is the fundamental use case)?
What is the point?
I think you are not looking at it from an unbiased mind. You are too much into your current design to see that this is very fundamental UI design use case.

@zoechi
Copy link
Contributor

zoechi commented Nov 11, 2016

If you tell me you can't build some kind of application because of this, then this sounds to me like you are not using Angular like it's supposed to be used.
I agree, that it is sometimes inconvenient but that doesn't make it high priority, and it also was already confirmed that they want to add it eventually.
So, I don't see your point.

@vinaysoni
Copy link

vinaysoni commented Nov 11, 2016

Sir, when you have a complex application, enterprises invest millions of dollars to build it.

One can build "some kind of application" with it. That kind of people are those who are just learning Angular2 or doing a proof of concepts.

But a framework that disregards such a basic tenet, needs to first realize the importance of what is missing.

What you are missing is that "Not destroying the components" is how applications are built today. You are adopting the approach of destroying - and expecting developers to waste their time to recreate that state, which is unnecessary and intrusive to the UI architecture. Why are you doing this? Is it just convenient for yourself. What would be the cost of this? Especially, when mainstream community finds out, then they may use the third party UI-Router. Only a novice developer will go to the work arounds, you mentioned. Seasoned UI developers can visualize the ditch that has been dug. So, the question is why is this not high priority? Angular2 is still in early stages. Later, the more people look at it, the more people will mock it. This is very fundamental to survival of the Router.

It is absolutely not about little inconvenience. One has to design every bit of the application differently with this serious limitation and one may run into issues that can never be implemented with the current approach.

@zoechi
Copy link
Contributor

zoechi commented Nov 11, 2016

But a framework that disregards a basic tenet,

This is absolutely not the case. As mentioned they plan to add this feature.

I still don't get your point. Seek out some framework Xyz in version 27.9 and be happy with it instead of complaining that the first release of of a brand-new framework has a missing feature.
Besides that I'm out of this discussion.

@vinaysoni
Copy link

Updated my comment above.

@vinaysoni
Copy link

"I still don't get your point. Seek out some framework Xyz in version 27.9 and be happy with it instead of complaining that the first release of of a brand-new framework has a missing feature. Besides that I'm out of this discussion."

You have raised a very important point without realizing it. I must explain why I don't pick up framework XYZ and be happy with it instead of complaining that the first release of of a brand-new framework has a missing feature.

I think Angular2 is revolutionary in many ways. It is this one limitation that screws it up really really bad. I hope you realize this.

Complaining is not bad if it can help avoid the blemish on an otherwise great framework? If I had read that this is high priority, I would not have written a single time, because I would know that this is being worked on. Once you say, that this is not high priority, I see the void between otherwise great framework and a framework with serious shortcoming.

You are inventing so many other features in the Router, that is admirable. But as a UI architect, I know that without this feature all the others are just glitter.

@zoechi
Copy link
Contributor

zoechi commented May 23, 2017

No matter how badly missed that feature is, if there are other more badly missed features where more developers are affected, those will get higher priority. There aren't unlimited resources, even for Google, to get everything done at once.

There was a custom reuse strategy added a while back that might address your problem.
I haven't tried it myself https://medium.com/@juliapassynkova/angular-2-component-reuse-strategy-9f3ddfab23f5, https://stackoverflow.com/questions/42383546/angular2-doesnt-work-custom-reuse-strategy-with-lazy-module-loading, https://www.softwarearchitekt.at/post/2016/12/02/sticky-routes-in-angular-2-3-with-routereusestrategy.aspx

@dewwwald
Copy link

dewwwald commented Jun 8, 2017

@zoechi, appreciate the work you and your team mates put in to Angular2.

I would like to see what I can do from my side to help this feature be implemented. Or I might be approaching this wrong.

I have the following routes

'foo/:hello/:world/:page', component: FooComponent
'foo/:hello/:world/', component: FooComponent

In the above case :page is basically a pagination of FooComponent. :world is a type and though :world changes the component FooComponent Remains the same (as you can gather).

I can use query params to paginate as that solved the problem. However, I would like the switching between routes that have matching components to not re-render all of the component. I think it will have a SEO benifit to have unique path-segments in place of query params (might be better I am still testing the theory).

So any suggestions would be great.
I will read the above resource.
However I just wanted to add my case as an illustration of when this happens and also be number in the devs with the problem count :).

Regards,
Dewald.

@zoechi
Copy link
Contributor

zoechi commented Jun 9, 2017

@dewwwald I'm not on the Angular team, just an individual contributor and Angular user (just to avoid misunderstandings)

@dewwwald
Copy link

dewwwald commented Jun 9, 2017

@zoechi, sorry for that. Even more so, being an independent contributor is a huge feat.

Any recommendations on the above?

@zoechi
Copy link
Contributor

zoechi commented Jun 9, 2017

Nope. I never had that requirement. I'm fine with keeping the state in a service and let Angular recreate the component.

@dewwwald
Copy link

dewwwald commented Jun 9, 2017

@zoechi That is exactly what I am doing.

However, the component subscribes to get the progress (percentage based), I am also initializing the component with 0%. So the service is like, 'oh a new subscriber, here is 50%' (trying to make the high-level discussion fun to read) and then css animation jumps from 0 to 50 after component recreation.

No matter I solved it with query parameters.

@DanielKucal
Copy link
Contributor

DanielKucal commented Jul 4, 2017

Hi @dewwwald, I think you might be interested how I achieved this goal by using custom router reuse strategy:

export class CustomRouteReuseStrategy extends RouteReuseStrategy {
    public shouldDetach(route: ActivatedRouteSnapshot): boolean { return false; }
    public store(route: ActivatedRouteSnapshot, detachedTree: DetachedRouteHandle): void {}
    public shouldAttach(route: ActivatedRouteSnapshot): boolean { return false; }
    public retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle { return null; }
    public shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
        return (future.routeConfig === curr.routeConfig) || future.data.reuse;
    }
}

And setting data: { reuse: true } for component route that should be reused. I described the details here: https://stackoverflow.com/questions/44875644/custom-routereusestrategy-for-angulars-child-module/44876414#44876414.

@dewwwald
Copy link

dewwwald commented Jul 4, 2017

@DanielKucal Thanks. I'll have a look.

@martinsik
Copy link
Contributor

I just stumbled upon the same issue even though the documentation says it re-uses components by default: https://angular.io/guide/router#observable-parammap-and-component-reuse

By default, the router re-uses a component instance when it re-navigates to the same component type without visiting a different component first.

@zoechi
Copy link
Contributor

zoechi commented Jul 25, 2017

@martinsik it does, but only when it's the same route (only route parameter change)
If you have 2 different routes with the same component, the component is destroyed and recreated by default.

@martinsik
Copy link
Contributor

@zoechi Hmm, I see. This didn't occur to me from the description. Anyway I tested it and it realy works the way you describe it. Thanks!

@abdel-ships-it
Copy link

@zoechi Can this behavior of component recreation be prevented?

@charanseeram
Copy link

charanseeram commented Oct 31, 2017 via email

@zoechi
Copy link
Contributor

zoechi commented Oct 31, 2017

@realappie see the links in #12446 (comment)

@abdel-ships-it
Copy link

@zoechi Thank you for the great sources, I have been reading a lot of github issues and been through a ton of commits to the point I lost track of whether the feature I need is properly implemented or not. Turns out it is, and here is a demo of that for anybody that finds it hard to implement.

To use it just add a reuse: true boolean in your router configuration.

@simeyla
Copy link

simeyla commented Jan 6, 2018

@realappie as others have said this only works because you are not changing to a different component altogether. Imagine a OrdersComponent and CustomersComponent. You can't switch between them without losing all unsaved state.

@kperdomoc
Copy link

kperdomoc commented Feb 26, 2018

I agree that this should work for a wider variety of scenarios. I have a complex component that can handle several type of urls and needs to remember the state of a tree widget that shows a hierarchy of data. Im using a service to keep that state and it's being a pain in the a**. Need to keep track of what nodes are downloaded, what nodes are expanded, etc. Lot of coding that could be saved if angular were more flexible about this.

@tommybananas
Copy link

tommybananas commented Apr 5, 2018

I know this isn't how I should accomplish this, but it actually works as far as reusing the component without any hacks in Angular 5. In this case /user and /user/edit actually reuse the component even though UserComponent does not have a router outlet at all and is a child of itself.

const routes: Routes = [
  {
    path: '',
    component: UserLayoutComponent,
    children: [
      { 
        path: 'user', 
        component: UserComponent,
        children: [
          { path: 'edit', component: UserComponent }   // I'm not sure this needs to be UserComponent
        ]
      }
    ]
  }
];

Any insight here? Does anyone know of another way to accomplish this without a placeholder component?

@mlc-mlapis
Copy link
Contributor

@tommybananas ... I think that this is a correct case and using ...

@tommybananas
Copy link

tommybananas commented Apr 5, 2018

@mlc-mlapis How could this be correct when I have a component as a child of itself that contains no router outlet? I find it hard to believe that the official solution to this issue is using a placeholder component. This is a pretty common use-case. Having the normal and edit states of a resource share a component.

@mlc-mlapis
Copy link
Contributor

mlc-mlapis commented Apr 5, 2018

@tommybananas ... you can use both URL directly ... which is fine ... probably as .../user/:id or .../user/:id/edit. The <router-outlet> directive is not present in UserComponent template, that is true ... but is there any limitation or a problem with that?

The component UserComponent is still able to recognize what is the current route and behave accordingly.

You can have even more routes combined ... .../user/:id/authorize, .../user/:id/.... and use still the same component.

Or you can use directly componentless route as ...

{ 
   path: 'user', 
   children: [
      { path: ':id', component: UserComponent },
      ...
   ]
}

@brianpetersencornell
Copy link

@tommybananas
I stumbled upon this thread, I came to the same implementation as you on my app (where the component is a child of itself).

It works, but I feel like this is a hack since it's working around the intention of the design. I hope I am wrong. But it makes me hesitant to go this direction and worried I should not depend on it for future releases.

Have you tried the solution @DanielKucal posted above? I will take a look and try tomorrow, but I use different data for each route I need to reuse. So I need the component to stay the same, but the data for the new route to be reflected. Maybe I can pass that through though.

I'm not sure if this is feature is still of importance.

My use case:
I am using a subset of my routing as a wizard. I am using a single component with multiple routes to data drive the view.
If I don't use this component as a child of itself approach, my components have to rebuild on every step. Its true I can cache the state in a service but it does flicker and is a bit slow to reconstruct the components. Especially since my steps pull in google maps, which I don't want to reload multiple times.

Open to any obvious suggestions I am missing. Thanks guys.

@brianpetersencornell
Copy link

Just tried @DanielKucal reuse strategy above and its works awesome. Enables me to control what I want to reuse very simply.

currently seems like the way to go and very simple.

Thanks @DanielKucal for posting and anyone else involved.

@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 Sep 13, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.