angular: onSameUrlNavigation doesn't re-load routed components

I’m submitting a…


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[x ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior

I’m trying to use the new onSameUrlNavigation flag in 5.1.1, and while it does repeat the RouterEvent cycle, it doesn’t trigger any lifecycle events in the routed components

Expected behavior

ideally if it’s going to trigger a reload, it should re-initialize any routed components.

Minimal reproduction of the problem with instructions

imports: [RouterModule.forRoot([ { path: “”, component: HomeComponent, pathMatch: “full” }, { path: “login”, component: LoginComponent }, ], { onSameUrlNavigation: “reload” })]

This configuration will not trigger lifecycle events in LoginComponent if there’s a link to LoginComponent somewhere on the page

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

This flag was added to support refresh, but developers would still have to listen to the router event cycle to be able to refresh any components this way

Environment


Angular version: 5.1.1


Browser:
- [x ] Chrome (desktop) version XX
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ x] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ x] IE version XX
- [ ] Edge version XX
 
For Tooling issues:
- Node version: XX  
- Platform:  

Others:

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 21
  • Comments: 83 (27 by maintainers)

Commits related to this issue

Most upvoted comments

Can’t we just have a “reload()” method? This is like buying a dairy farm to get an ice cream cone.

@TylerDev6 … from where did you take that onSameUrlNavigation should introduce triggering of life-cycle hooks on components? As I know from source code it was introduced to re-invoke only guards & resolvers.

Sorry … your issue is the feature issue. 👌

@mlc-mlapis the angular router documentation itself mentions “refresh”… it’s not a real refresh if the components don’t reload…

https://angular.io/api/router/ExtraOptions

As noted by @kapunahelewong, we need to clarify the expectations in the docs. As for being able to actually reload the components, this seems to be possible by providing your own RouteReuseStrategy along with {onSameUrlNavigation: 'reload'} as in this example: https://stackblitz.com/edit/angular-ivy-router-base-2hgd2t. Please correct me if I’m wrong, but this appears to accomplish the described goal.

@mlc-mlapis I understand what you’re saying… but:

  • the one step further is again a separation of concerns… it’s easy to say ‘observe everything’ until you have 50 things to observe and you can’t reason on when / why things change anymore…
  • not everyone uses observables (I assume you mean async / observables) – it’s a nice goal, but the complexity level there is high for many devs, so they do not.
  • if you have a large application where someone assumed things could not change (i.e. it was hardcoded) – and then someone comes along later and says ‘we need to make this configurable’ … do you change 100 components to respond to live changes? … you can, but the complexity vs. benefit is very low.

On the other side of the coin – I don’t see the harm in having a reload… it’s a perfectly valid workflow. I’m just asking for the obtuse workflow outlined in various threads on this subject to be wrapped into a reload method.

@rainstormza … hmm, just I forgot to say that there is another property which affects the behavior of re-running guards and resolvers … when onSameUrlNavigation: true is set … then … it is runGuardsAndResolvers … so there has to be runGuardsAndResolvers: 'always' (… there are also 'paramsChange' or 'paramsOrQueryParamsChange' possibilities) set on each route definition which you want to affect.

You can see it in action on this Stackblitz demo: https://stackblitz.com/edit/onsameurlnavigation?file=app/app-routing.module.ts

Without runGuardsAndResolvers only Router events are invoked.

Another vote for an easy router reload() method, please!

I understand why the resolver or subscribe to an event methods are suggested. But for myself, reloading is only used for rare, secondary events. I (and my users) don’t need anything fancy or have blazing fast performance expectations.

Resolvers feel nice at first but are difficult and unintuitive to maintain (my experience with two eng teams that both tried them) and make page display slower. And event-subscribing boilerplate is convoluted and irritating to add and maintain to every component in an app just to enable reloading.

Forcing people to use and maintain these patterns for “smart refreshing” feels pretty bad, when it could be as easy as router.reload(). I don’t think anyone would say “hey! I thought reload() was going to be more performant than reloading!” but you do have a lot of people here saying “please allow this.”

Ahh! I’m getting more and more tired of endless threads like this that go on for years. Especially when a simple API fix would wipe the slate clean.

My number one reason for wanting an component to recreate itself in full is data integrity / safety and for beginners ease of use.

Ease of use first (less important to me TBH) - when you start learning about Resolve you basically HAVE to use Observables - or you don’t get new values when the route changes (if you try to use snapshot.data). For beginners it would be nice to just take snapshot.data in the ngInit and start with a clean page.

I’m developing a CRM admin console - so I have customers with orders, messages, membership settings etc. I have resolvers that load the most important data, but I rely on a ‘ViewModel’ class to manage loading dependencies and handling errors.

I also use Observables extensively with a lot of async pipes, where often a value is derived from multiple inputs. Maybe there’s a list of orders$ and a list of shippingAddresses$. If you’re not careful (assume these two Observables take varying time to initialize) you could end up merging a previous customer’s orders with a newly selected customer’s shipping addresses when changing route.

Easy to fix - probably - but then you have to make sure every observable is reset when a new route is set and it adds complexity. If you make a mistake somewhere and end up in this scenario the results could be a disaster.

The point being sometimes it’s far safer to just start from a blank page on a route change and just not have to worry about such things. And when switching to a new customer I’m already recreating all the child components anyway - so why this big fuss about performance on the parent I don’t know.

So it’s been over a year since this issue was opened. Is this on the roadmap at all? Or should we just implement one of the solutions (hacks) mentioned in this thread and elsewhere? I feel like this feature is super useful and (from my naive knowledge of Angular) wouldn’t be that difficult to implement?

@We-St … then I missed the idea … when nothing has been changed than what do you want to refresh?

If you navigate manually to the route, you can always navigate to an empty route first, then navigate to the route you want.

this.router.navigate(['/reload']).then(() => { this.router.navigate([ ... ]); })

Comon, This feature is just to let you program more precisely to control over the events, not to delegate everything such as (your idea) to reload a page… If so, you can suggest another directive property such as onSameUrlNavigation: reloadAll

The problem is that those cases represent small % of the whole issue

I’m not sure exactly what you mean here, but applying it to the performance implications that seem to be the main objection - I’m not sure the typical user, perhaps anyone, would even notice in the majority of cases. Or maybe the very savvy user would see a flicker and realize, but it would be so fast that even the savvy user wouldn’t care.

The main argument seems to boil down to others dictating the performance characteristics of my app. Being opt-in, the documentation has the opportunity to clearly state that this may negatively impact performance. I’m perfectly fine with Angular choosing a default that is not in line with how the web has always worked, but I also think there should be an easy config to enable the requested behavior.

Why not just add a ‘forceReload’ method to the router that re-initializes the entire route IN ADDITION to leaving the other mechanisms in place. There are legitimate scenarios for all cases. You can even name the method ‘shameOnMeForForcingAReloadInThisFashion’ if it makes some purist feel better. This is super aggravating to not have. This is forcing me to write a lot of extra code in areas that should not be concerned with this sort of thing or write some silly ‘/reload’ page/route/bounce/back/again thing. Grr 😠

👍

I think a refresh is what most everyone expects, end users and developers alike. It would be nice to have a simple way to enable this wherever desired. My specific use case is for settings screens like the one in github. image

If you click Options on the left, pretty much everyone expects the form to reset (and usually with updated data if there are updates). The same with Settings up top (except it would also take you back to Options if you were on a different settings page). Ideally, there shouldn’t be anything (or very little) a dev has to do to get this behavior.

Why does this exists only on forRoot? Why can’t forChild accept the additional parameter as well?

What i need for my use-case (in my case a side by side master/detail view) is for only the child resolver and component to be reloaded, as i don’t want my master list to be rebuilt every navigation.

Any chance you could help me extend your solution to accomplish this? Thanks!

@AlexanderSesslerPorabo I believe what you want is for your shouldReuseRoute to be return future.children.length !== 0; rather than return false. i.e. reuse the activated route if it has children. https://stackblitz.com/edit/angular-ivy-router-base-fund7g?file=src%2Fapp%2Fapp.module.ts

@kdubious … why runGuardsAndResolvers: 'always' is not enough? It’s all about resolvers.

@mlc-mlapis It’s not about making this the default behavior, but about giving people the choice to sacrifice performance for simpler code if they think it makes sense in their given use case.

Your comparison with reusing a component instance on route params change is actually the best argument for the change: it’s easy to react to a change from product/5 to product/6, but very hard to react to a change from product/5 to product/5 (e.g. when the user clicks the same menu item again).

For me, it would also work to keep the same component instance and configure the route to issue duplicated params, e.g. that route.params can emit the same value multiple times if the user triggered this behavior.

@We-StonSameUrlNavigation + runGuardsAndResolvers work together … read https://medium.com/engineering-on-the-incline/reloading-current-route-on-click-angular-5-1a1bfc740ab2

And the general explanation is … why to destroy already existed component instance and create a new one for the same case when it means performance degradation? It is the exactly the same behavior as using routes pattern /product/:id where the same component instance is also kept for /product/5, /product/6, …

So you should re-init the component on the base of some emitted event (resolver/guard) and not on the base of OnInit hook because of the same component instance.

@vicb – any opinion on this?

@chadbr … yes, I mean async / observables logic. You are right that it is easier to suppose that there is only one source of any change … and we also supposed that before Angular 2.x … so no live changes. But with Angular we have changed our thinking.

It is also true that it could be relatively easy to add reload() method to Angular API and I am not sure what was the primary reason why Victor didn’t do it. It is just my guess that the reason was his conceptual point of view. 😄

Agree. It’s frustrating to not have this. I find I’m getting frustrated with Angular 2+ in general… “compiling” TS (JS) into this framework is a risk we all take by using Angular 2+. Having little control over the roadmap makes that risk even riskier.

@chadbr window.location.reload(true); is not cross platform. It’ll work for simple websites in a browser, but not necessarily for PWAs, Universal SSR, mobile-platform libraries that use Angular, etc.

I think this thread should probably be closed… I don’t see Angular supporting it.

fwiw – this gets it done for us… ugly but effective.

window.location.reload(true);

@kdubious … it’s working as following:

  • you are attaching a resolver to a route (it runs always because of runGuardsAndResolvers: 'always')
  • your Dashboard component can read (subscribes to) that resolver data (this the smart component)
  • those data can be passed to any other child component (those are dumb ones) either via @Inputs
  • or if more suitable for any reason via some other service injected into those children.

That’s all.

Status: docs team needs to clarify documentation on this point.

Maybe angular team could add this reload component option, but I agree that by design it doesn’t make much sense. In case it may be useful to anyone, I managed to react appropriately to onSameUrlNavigation event by intercepting the NavigationStart event and comparing router.url and event.url. If they are equal then you could do something like reloading your component. In my case I created a navigatedSameRoute: EventEmitter<string> which I fire when NavigationEnd takes place and my component subscribes to that EventEmitter and responds by reloading, like this:

  // router-navigations-manager.component.ts (for example...)
  private currentNavUrl: string;
  private nextNavUrl: string;
  @Output() public navigatedSameUrl: EventEmitter<string> = new EventEmitter<string>();

  constructor(private router: Router) { }

  ngOnInit() {
    this.router.onSameUrlNavigation = 'reload';
    if (!this.eventsSubscription || this.eventsSubscription.closed) {
      this.eventsSubscription = this.router.events.subscribe(ev => {
        if (ev instanceof NavigationStart) {
          this.currentNavUrl = this.router.url;
          this.nextNavUrl = ev.url;
        } else if (ev instanceof NavigationEnd) {
          if (this.currentNavUrl && this.currentNavUrl === this.nextNavUrl) {
            this.navigatedSameUrl.emit(this.nextNavUrl);
          }
          this.currentNavUrl = undefined;
          this.nextNavUrl = undefined;
        }
      });
    }
  }

  ngOnDestroy() {
    if (this.eventsSubscription) {
      this.eventsSubscription.unsubscribe();
    }
  }

shameOnMeForForcingAReloadInThisFashion lol… although i am on the suffering end of this ordeal, but I can see what Agnular people were thinking when they designed it that way, see “route” is just another observable in an SPA, like all observables you add to your component, imagine having a subject in your component, when you next it re-inializes another component? devistating… so I guess it just the way it is, if you have multiple components that feed from the route, make sure you tie them to the main route observable

@ayyash Not sure about your concrete case conditions, but there are different settings for using onSameUrlNavigation, which allows you to re-run guards and resolvers to re-init the same component instance, instead of destroying and re-instantiating it. All are based on the subscriptions to the changes on URL. The real difference is mainly in our thinking. Angular generally prefers reactive ways, where it’s possible.

@mlc-mlapis I don’t use resolvers at all… what am I missing? (off to read more documentation) I was leaning toward using @ViewChild for these other components that need refreshing.

EDITED / ADDED: In my case, the Route is for the Dashboard. I don’t pass the data to the 2 child components, they load the data themselves. Those components are used in other places, so it makes sense to me for them to get data on their own. Those 2 components are not loaded via a route. I’m not sure how a Resolver would even work here.

My use case is changing the user context globally for all components and here’s my (ugly) workaround: In the template I added something like…

<router-outlet *ngIf="reload"></router-outlet>
<router-outlet *ngIf="!reload"></router-outlet>

… and then in the component instead of navigating I have …

this.reload = !this.reload

Works but I’d prefer a way provided by the framework to do this. The same can be achieved with a directive (a bit nicer):

@Directive({ selector: '[myRefreshOnChange]'})
export class RefreshOnChangeDirective {

  private value: any;

  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef) { }

  @Input() set myRefreshOnChange(value: any) {
    if (this.value !== value) {
      this.viewContainer.clear();
      this.viewContainer.createEmbeddedView(this.templateRef);
      this.value = value;
    }
  }
}

@mlc-mlapis in this simple case, we have a “pop-in” component that allows users to change ~10 settings (colors, units of measure, internationalization, etc.etc.) –

When some of these settings are changed, we’d like to simply reload the control that happens to be in focus. Each of the controls have various services that “own” these settings and provide “the correct thing” to the components.

We could add every setting change as some kind of event that every component in the system responds to – but how about some separation of concerns? Does every component need to know about a possible data change in every service? Other component / service? It seems unreasonable to me… not to say it can’t be done, but I don’t think it should be done. A reload() makes all the code so much cleaner.

Thanks, Chad

Thank you @mlc-mlapis . That makes sense to me. Using runGuardsAndResolvers and Router events should be exactly what I need. Appreciate the advice!

@bluefire2121 … ah, but onSameUrlNavigation has nothing to do with creating a new instance of the component. You can just get router events and then it is on you what you’ll do (and for example emit on observable / subscribe stream something to let the component instance to know). But you can use runGuardsAndResolvers on each route differently … and re-run the resolver to get refreshed data … so it is exactly what you want to do.

Doesn’t setting the router as a singleton at the root mean that onSameUrlNavigation will work the same throughout an entire application? If so, then that’s not what I want. I want onSameUrlNavigation to work differently at different levels of my app.