Angular 2 Component Reuse Strategy
The default behaviour of Angular 2 router is to reuse the components if the current and future routes are the same. This post shows how this strategy affects the component implementation and how this strategy can be modified.
Stale Component
Here is an example of the component that shows the stale data. When the application route is changed from ‘/detail/1’ to ‘/detail/2’ the DetailStaleComponent still shows the initial param 1.
import { Component, OnInit } from '@angular/core';
import {ActivatedRoute} from '@angular/router';
@Component({
selector: 'app-detail-stale',
template: '<p>detail stale for {{id}} param</p>'
})
export class DetailStaleComponent implements OnInit {
id: string;
constructor(private route: ActivatedRoute) {
}
ngOnInit() {
this.id = this.route.snapshot.params['id'];
}
}
Subscribe to changes
The component should subscribe to route params changes and update its model/view when the application route is changed from ‘/detail/1’ to ‘/detail/2’. The component’s ngOnInit method gets access to ActivatedRoute params observable and uses async pipe to keep the param up to date.
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');
}
}
RouteReuseStrategy
Angular 2 route allows to change the default reuse strategy. This example shows how custom RouteReuseStrategy helps to solve the stale data for situation when the component does not use subscription.
Angular 2 uses DefaultRouteReuseStrategy in case the application does not provide a custom one. DefaultRouteReuseStrategy implements RouteReuseStrategy interface that defines shouldReuseRoute method that is used by router to make the reuse decision.
export abstract class RouteReuseStrategy {
/** Determines if this route (and its subtree) should be detached to be reused later */
abstract shouldDetach(route: ActivatedRouteSnapshot): boolean;
/** Stores the detached route */
abstract store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void;
/** Determines if this route (and its subtree) should be reattached */
abstract shouldAttach(route: ActivatedRouteSnapshot): boolean;
/** Retrieves the previously stored route */
abstract retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle;
/** Determines if a route should be reused */
abstract shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean;
}
Let’s defined our own CustomRouteReuseStrategy that does’t allow to reuse our DetailSameComponent component. DetailSameComponent component is identical to DetailStaleComponent defined above.
import {DefaultRouteReuseStrategy} from '@angular/router/src/router';
import {ActivatedRouteSnapshot} from '@angular/router';
export class CustomRouteReuseStrategy extends DefaultRouteReuseStrategy {
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
let name = future.component && (<any>future.component).name;
return super.shouldReuseRoute(future, curr) && name !== 'DetailSameComponent';
}
}
The strategy is an Angular 2 service that should be registered in a module.
@NgModule({
declarations: [AppComponent, ListComponent, DetailReusableComponent, DetailStaleComponent, DetailSameComponent],
imports: [BrowserModule, FormsModule, HttpModule, RouterModule.forRoot(routes)],
providers: [
{
provide: RouteReuseStrategy,
useClass: CustomRouteReuseStrategy
}
],
bootstrap: [AppComponent]
})
export class AppModule {
}
And here is a result — the component shows the correct data after the route is changed from ‘/detail/1’ to ‘/detail/2’
Here is link to angular2-router-reuse github repository that contains the full code.