87

I am currently writing my first Angular 2 Application. I have an OverviewComponent which has the following simple template:

<div class="row">
  <div class="col-lg-8">
    <router-outlet></router-outlet>
  </div>
  <div class="col-lg-4">
    <app-list></app-list>
  </div>
</div>

when accessing the url / my router redirects me to /overview which then loads a map within the router-outlet. The <app-list> has a list of clickable items which triggers a <app-detail> to be displayed instead of the app component. Therefor I pass the id of the referring json file in the url like that: /details/:id (in my routes).

All of the above works totally fine. If I now click on one of the list items the details are shown, BUT when I select another list element the view doesn't change to the new details. The URL does change but the content is not reloaded. How can I achieve a Reinitialization of the DetailComponent?

hGen
  • 2,235
  • 6
  • 23
  • 43

14 Answers14

179

You can change the routeReuseStrategy directly at the component level:

constructor(private router: Router) {

      // force route reload whenever params change;
      this.router.routeReuseStrategy.shouldReuseRoute = () => false;

}

Likewise, the reuse strategy can be changed globally.

This does not necessarily address your problem directly but seeing how this question is the first search result for "angular 2 reload url if query params change" it might save the next person from digging through the github issues.

Nik
  • 7,086
  • 6
  • 36
  • 52
  • Has that been implemented since the first angular2 release? Or is it part of the newer versions? – hGen May 14 '18 at 15:56
  • Finally! After a lot of search, this was the solution for Angular 5. Thanks! – Magor Menessy Jun 28 '18 at 16:51
  • 5
    Could not thank you enough for this. I'll add that after adding this change globally, you shloud **really** check if all your routes are OK, since one of mine just didn't work anymore. – Shikatsu Kagaminara Jul 31 '18 at 14:51
  • Nice, love it when I lean something unexpectedly! –  Jan 07 '19 at 00:18
  • 2
    works for me with angular 7 after searching for a fix for a while. thanks. – Laurence Jan 12 '19 at 15:54
  • 2
    Be careful, this approach brokes routerLinkActive directive. – lilo.jacob Jul 09 '19 at 11:47
  • 1
    Yes, I am concerned about the impact this could leave? Are we overriding this for only this component? Or are we doing it for everything else? – Mohy Eldeen Nov 06 '19 at 23:52
  • 10
    This is overriding Route Reuse strategy globally, and not just for the current component - be careful with this approach. – seidme Aug 18 '20 at 07:52
  • 2
    This setting makes layout components to be destroy and reload on navigation. It will be expensive process if APIs are called on layout components. – Mr.Zon Aug 28 '20 at 09:19
  • 1
    I was already going crazy looking for a solution to recreate the component by repeating the same route and this worked perfectly.. thank you very much! _[**Angular 15.0.4**]_ – Hugo Leonardo Jan 10 '23 at 19:50
79

As per the first final release, this is resolved.

Just pay much attention to correctly reset the state of the component when the parameter changes

this.route.params.subscribe(params => {
    this.param = params['yourParam'];
    this.initialiseState(); // reset and set based on new parameter this time
});
gpanagopoulos
  • 2,842
  • 2
  • 24
  • 19
  • While this is a suggested solution, the issue I had was my component (which has the above code implemented) was being loaded after the route was changed, and thus the code in this promise wasn't being fired – jarodsmk Jun 30 '17 at 06:12
  • 2
    This is by far the more elegant and angulary solution, although I'd rather exchange `resetComponentState()` by a more generalized `initialize` function which gets called whenever the component is created or parameters are altered. – hGen Sep 28 '17 at 10:59
  • 1
    valid point @hGen. I changed it to initialise to indicate that it is the one called also on first time load. – gpanagopoulos Sep 28 '17 at 12:34
  • is "initlialize" a built in angular method? – Ayyash Oct 04 '17 at 15:24
  • 1
    no, it is your custom method to set the state of the component for the given parameter – gpanagopoulos Oct 04 '17 at 15:44
  • 6
    'route' refers to an instance of ActivatedRoute – CRice Jan 22 '18 at 07:35
20

Another alternative that should be added here is to provide a RouteReuseStrategy to your module.

providers: [
  {
    provide: RouteReuseStrategy,
    useClass: AARouteReuseStrategy
  }
]

The default behaviour of the router is to reuse the route if the configuration is the same (which is the case when only changing the :id param in this question). By changing the strategy to not reuse the route, the component will be reloaded, and you do not have to subscribe to route changes in the component.

An implementation of the RouteReuseStrategy could be like this:

export class AARouteReuseStrategy extends RouteReuseStrategy {
  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    return false;
  }
  store(route: ActivatedRouteSnapshot, handle: {}): void {

  }
  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    return false;
  }
  retrieve(route: ActivatedRouteSnapshot): {} {
     return null;
 }
 shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
   return false; // default is true if configuration of current and future route are the same
 }
}

I've written some about it here too:

https://pjsjava.blogspot.no/2018/01/angular-components-not-reloading-on.html

Peter Salomonsen
  • 5,525
  • 2
  • 24
  • 38
  • 2
    This should be an accepted answer, since it does not require to change component behavior and can be defined globally. – Ivan Zverev May 31 '18 at 11:09
20

Import Router in your Angular 7 project.

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

Create an object of the Router

constructor(private router: Router) {

}

Use routeReuseStrategy to detect router parameter change

ngOnInit() {
    this.router.routeReuseStrategy.shouldReuseRoute = () => {
      // do your task for before route

      return false;
    }
}
Abhisek Dutta
  • 257
  • 2
  • 4
17

Use this on your constructor()

this.router.routeReuseStrategy.shouldReuseRoute = () => false;
Prashant Pimpale
  • 10,349
  • 9
  • 44
  • 84
Abdus Salam Azad
  • 5,087
  • 46
  • 35
  • This works just perfect. It should be the accepted answer. Going straight to my notes. Thanks Abdus. – Danielle Mar 24 '22 at 21:00
7

I don't know whether there is an answer to the problem similar to the one I'm going to propose right here, so I'll do it anyway:

I managed to achieve a 'fake' reload the following way.

What I basically did is creating a Component which redirects me to the 'real' component I want to use:

@Component({
  selector: 'camps-fake',
  template: ''
})
export class FakeComponent implements OnInit {

  constructor(private _router:Router,
              private _route:ActivatedRoute)
  { }

  ngOnInit() {
    let id:number = -1;
    this._route.params.forEach((params:Params) => {
      id = +params['id'];
    });

    let link:any[] = ['/details', id];
    this._router.navigate(link);
  }

}

So by selecting a list item the router will navigate to /fake/:id which just extracts the id from the URL and navigates to the 'real' component.

I know there might be an easier, or more fancy way, but I think this solution works pretty well since the fake doesn't really attract attention. Just the 'flashing' when the page reloads is a negative aspect but as far as my css knowledge reaches there might be some transition to cover that.

rjdkolb
  • 10,377
  • 11
  • 69
  • 89
hGen
  • 2,235
  • 6
  • 23
  • 43
  • Well, thanks a lot for this tip ! It's a shame that Angular doesn't support this by default with some kind of event. – YoannFleuryDev Feb 08 '17 at 22:46
  • It has been 6 months is there any news on this? I am using your approach currently but it's not seo friendly. – Raymond the Developer Feb 12 '17 at 23:55
  • @RaymondtheDeveloper Not that I would know. I ran into the problem again today and gave google another shot with the exact same results. – hGen Feb 14 '17 at 11:05
  • 1
    @YoannFleuryDev Angular is open source, which means we all share the shame :) – sabithpocker Aug 21 '17 at 14:37
  • 1
    This worked for the first several problem sets but is not very angulary. Take a look at @gpanagopoulos suggestion which'll be the official accepted solution fronm now on. – hGen Sep 28 '17 at 11:02
7

Is possible to detect any change in the parameters received, in my case I load the information using a Resolve so I don't need the parameter (only detect if it change). This is my final solution:

public product: Product;
private parametersObservable: any;

constructor(private route: ActivatedRoute) {
}

ngOnInit() {
  this.parametersObservable = this.route.params.subscribe(params => {
    //"product" is obtained from 'ProductResolver'
    this.product = this.route.snapshot.data['product'];
  });
}

//Don't forget to unsubscribe from the Observable
ngOnDestroy() {
  if(this.parametersObservable != null) {
    this.parametersObservable.unsubscribe();
  }
}
Alexis Gamarra
  • 4,362
  • 1
  • 33
  • 23
  • 2
    I don't think unsubscribing is needed as the Router manages the observables it provides and localizes the subscriptions. They are cleaned up when the component is destroyed. See: https://angular.io/tutorial/toh-pt5#do-you-need-to-unsubscribe – Tohnmeister Jun 30 '17 at 13:52
  • I concur, though not unsubscribing from any subscription always makes me nervous. – Matt Eland Mar 31 '18 at 22:48
5
this.route.paramMap.subscribe(params => {
  //fetch your new parameters here, on which you are switching the routes and call ngOnInit()
  this.ngOnInit();
 });

You just need to call ngOnInit() from inside the paramMap and it will initialise the whole page with newly loaded data.

cyperpunk
  • 664
  • 7
  • 13
  • 1
    simplest, least error prone and most robust answer. Does not suffer from additional need of managing new subscriptions and taking into consideration (and possibly refactoring) other mechanisms that you may have hooked into router/modules (which actually might be a big deal as it was in my case). Thanks! P.S. ```routeReuseStrategy.shouldReuseRoute -> false``` did not work for me – azrahel Nov 21 '19 at 14:18
4

hope this will help.

constructor(private router: Router){
 // override the route reuse strategy

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

 this.router.events.subscribe((evt) => {
    if (evt instanceof NavigationEnd) {
       // trick the Router into believing it's last link wasn't previously loaded
       this.router.navigated = false;
       // if you need to scroll back to top, here is the right place
       window.scrollTo(0, 0);
    }
});

}
Robert
  • 5,278
  • 43
  • 65
  • 115
3

I didn't find that any of these was a good and thorough solution for Angular 8. Some suggestions ended up creating infinite loops that resulted in, ahem, a stack overflow. Others are just too hacky for my tastes. I found a good solution online but since I can't post just a link here, I'll do the best to summarize what I did based on the link and why I think it's a solid solution. It allows you to just affect certain routes that need the behavior and not others and you don't need to roll any custom classes to make it work.

From Simon McClive's solution at https://medium.com/engineering-on-the-incline/reloading-current-route-on-click-angular-5-1a1bfc740ab2

First, modify your app-routing module configuration:

@ngModule({ imports: [RouterModule.forRoot(routes, {onSameUrlNavigation: ‘reload’})],
exports: [RouterModule] })

Next, modify the routes you want to affect. If you aren't using authentication you can omit the canActivate parameter:

export const routes: Routes = [
 {
   path: ‘invites’,
   component: InviteComponent,
   children: [
     {
       path: ‘’,
       loadChildren: ‘./pages/invites/invites.module#InvitesModule’,
     },
   ],
   canActivate: [AuthenticationGuard],
   runGuardsAndResolvers: ‘always’, //there are three options for this - see Simon's post. 'Always' is the heaviest-handed and maybe more than you need.
 }
]

Lastly, update your class to listen for the navigation event and act accordingly (being sure to unregister the listener on the way out):

export class AwesomeComponent implements OnInit, OnDestroy{

 // ... your class variables here
 navigationSubscription;

 constructor( private router: Router ) {

   // subscribe to the router events and store the subscription so
   // we can unsubscribe later

   this.navigationSubscription = this.router.events.subscribe((e: any) => {
     // If it is a NavigationEnd event, re-initalize the component
     if (e instanceof NavigationEnd) {
       this.myInitFn();
     }
   });
 }

 myInitFn() {
   // Reset anything affected by a change in route params
   // Fetch data, call services, etc.
 }

 ngOnDestroy() {
    // avoid memory leaks here by cleaning up
    if (this.navigationSubscription) {  
       this.navigationSubscription.unsubscribe();
    }
  }
}
Newclique
  • 494
  • 3
  • 15
2

I solved it by using event, if the child component send a new link, and emit an event, then the parent can find the change and call to some reload function, which will reload the necessary data. Another option is to subscribe to the route parameters, and found when it change But I do think that the guys from angular2 should think about adding parameters to the router.navigate function, which can force the reload. (forceReload=true)

Community
  • 1
  • 1
user3728728
  • 885
  • 2
  • 11
  • 14
1

An idiomatic approach could be to use Observables and the | asyc pipe in your template.

(taken from https://medium.com/@juliapassynkova/angular-2-component-reuse-strategy-9f3ddfab23f5 - read more for details )

import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/pluck';

@Component({
  selector: 'app-detail-reusable',
  template: `<p>detail reusable for {{id$| async}} param </p>`
})
export class DetailReusableComponent implements OnInit {
  id$: Observable<string>;

  constructor(private route: ActivatedRoute) {
  }

  ngOnInit() {
    this.id$ = this.route.params.pluck('id');
  }
}

If you are fetching further details from a REST api, you can use switchMap for example:

import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/pluck';

@Component({
  selector: 'app-detail-reusable',
  template: `<ul><li *ngFor="let item of items$ | async">{{ item.name }}</li></ul>`
})
export class DetailReusableComponent implements OnInit {
  items$: Observable<string[]>;

  constructor(private route: ActivatedRoute) {
  }

  ngOnInit() {
    this.items$ = this.route.params.pipe(
      pluck("id"),
      switchMap(id => this.http.get<string[]>(`api/items/${id}`))  // or whatever the actual object type is
    );
  }
}

The | async pipe will automatically subscribe and the id$ or items$ observable will update when the route parameter changes triggering an API data fetch (in the items$ case) and updating the view.

Quang Van
  • 11,475
  • 12
  • 57
  • 61
0

This is currently not directly supported. See also https://github.com/angular/angular/issues/9811

What you can do is something like

<div *ngIf="doShow" class="row">
  <div class="col-lg-8">
    <router-outlet></router-outlet>
  </div>
  <div class="col-lg-4">
    <app-list></app-list>
  </div>
</div>
doShow:boolean: true;

constructor(private _activatedRoute: ActivatedRoute, private _router:Router, private cdRef:ChangeDetectorRef) {
  _router.routerState.queryParams.subscribe(
    data => {
      console.log('queryParams', data['st']); 
      this.doShow = false;
      this.cdRef.detectChanges();
      this.doShow = true;
  });
}

(not tested)

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Thanks for the quick response but I solved the problem in a different way, see my answer. – hGen Aug 16 '16 at 12:07
0

Put this on the constructor was by far the best solution

this.router.routeReuseStrategy.shouldReuseRoute = () => false;

However as it update the behavior of the reuse strategy for the whole app ! I tried several other approach to make a clean fix for this without having to reinvent the wheel.

The conclusion is below your eyes you just need to set the value to true when destroying the component

ngOnDestroy() {
    this.router.routeReuseStrategy.shouldReuseRoute = () => true;
}
Sabri
  • 112
  • 9