Skip to main content

How to use Angular RouteReuseStrategy to create snapshots so you can back to previous pages without reloading data


Github of this  exmaple.

 There are a lot of practical scenarios when you load a list of items then go to the detail of an item, when you hit the back button you don't want to reload the item list again.

Angular provides you a method to do exactly that. With RouteReuseStrategy when you hit the back button it takes you right back to where you were before without reloading anything. Of course, you can choose to do this or not for any page and it super easy to set up.

To do this you need to create a class implement RouteReuseStrategy from @angular/router. There are 5 methods you need to implement: shouldDetach, store, shouldAttach, retrieve, shouldReuseRoute. Let's take a closer look at these methods.

shouldDetach

shouldDetach(routeActivatedRouteSnapshot): boolean
When you go to the next page, this method will be trigged, it decides whether the current route (a page) should be detached. In other words, Angular takes a snapshot of it and saves it in memory so when you go to the next page, you hit the back button, it will take you right back to this page with reloading data.

store
store(routeActivatedRouteSnapshothandlerDetachedRouteHandle):void
If a route should be detached (shouldDetach returns true) this method will be triggered. In this method you going to tells Angular what and how to store the route.

shouldAttach
shouldAttach(routeActivatedRouteSnapshot): boolean
When you go back to the previous page by clicking the back button of the browser or using location.back() or history.back(). This method is triggered and it decides whether the page you are going back to should be attached or not, of course you can only attach the page if you have detached it (saved it) before.

retrieve
retrieve(routeActivatedRouteSnapshot): DetachedRouteHandle
This method go to where you saved the snapshot and take it out.

shouldReuseRoute
shouldReuseRoutefutureActivatedRouteSnapshotcurrentActivatedRouteSnapshot): boolean

This method determines if a route should be reused.

Implementation
You can use console.log in the class implemented RouteReuseStrategy to log route data when navigating to see how things work.
You need to do 3 things to make RouteReuseStrategy works.
  • Create a class implemented RouteReuseStrategy
  • Provide this provider (class) in app.module.ts
  • Mark the route you want to detach.

Create a class implemented RouteReuseStrategy
Create a class named CustomRouteReuseStrategy and paste my code:
(you can choose the class name you like or custom my default code)

import { RouteReuseStrategyDetachedRouteHandleActivatedRouteSnapshot } from '@angular/router';
import { ComponentRefInjectable } from '@angular/core';

@Injectable()
export class CustomRouteReuseStrategy implements RouteReuseStrategy {
  private handlers: { [keystring]: RootHandler } = {};
  shouldDetach(routeActivatedRouteSnapshot): boolean {
    return this.isDetachable(route);
  }

  store(routeActivatedRouteSnapshothandlerDetachedRouteHandle) {
    const storeKey =  this.getStoreKey(route);
    if (handler) {
      // I need to keep track of the time the route is stored so I added storeTime.
      const rootHandler = {
        handle: handler,
        storeTime: +new Date()
      };
      this.handlers[storeKey] = rootHandler;
    }
  }

  shouldAttach(routeActivatedRouteSnapshot): boolean {
    const storeKey =  this.getStoreKey(route);
    if (this.isAtachable(routestoreKey)) {
      // you can retrun true only
      // clearNewerHandlerOnAttach is optional
      // when load the snapshot (attach old route) I only want to keep routes stored before this route
      // and delete routes stored after this route.
      // for exmaple, if i go to product list and then product detail and then saler information
      // if product list, product detail and saler information are all detachable.
      // when I back from saler information to product detail I don't want to store saler information
      // and when I back from product detail to product list I don't want to store product detail route
      // because I when I go back to product list and then go to the same product detail again
      // I want to load new data. Why?
      // I think people rarely go back and fort 2 pages multiple time
      // by deleting unnecessary stored route we save memory.
      this.clearNewerHandlerOnAttach(this.handlers[storeKey].storeTime);
      return true;
    }
    return false;
  }

  retrieve(routeActivatedRouteSnapshot): DetachedRouteHandle {
    const storeKey =  this.getStoreKey(route);
    return this.handlers[storeKey]?.handle;
  }

  shouldReuseRoutefutureActivatedRouteSnapshotcurrentActivatedRouteSnapshot): boolean {
    return future.routeConfig === current.routeConfig;
  }

  private getResolvedUrl(routeActivatedRouteSnapshot): string {
    return route.pathFromRoot
        .map(v => v.url.map(segment => segment.toString()).join('/'))
        .join('/');
  }

  private getStoreKey(routeActivatedRouteSnapshot): string {
    const baseUrl = this.getResolvedUrl(route);
    const childrenParts = [];
    let deepestChild = route;
    while (deepestChild.firstChild) {
        deepestChild = deepestChild.firstChild;
        childrenParts.push(deepestChild.url.join('/'));
    }
    return baseUrl + '////' + childrenParts.join('/');
  }

  // true if we mark this route shouldDetach:true
  // see it in route config
  private isDetachable(routeActivatedRouteSnapshot) {
    if ( route?.routeConfig?.data?.shouldDetach) {
      return true;
    }
    return false;
  }

  private isAtachable(routeActivatedRouteSnapshotstoreKeystring) {
    if (this.isDetachable(route) && this.handlers[storeKey]?.handle) {
      return true;
    }
    return false;
  }

  /*
  When the user goes back (attach a root)
  I want to clear newer stored roots.
  */
  private clearNewerHandlerOnAttach(storeTimenumber) {
    const handlerKeys = Object.keys(this.handlers);
    handlerKeys.forEach(k => {
      if (this.handlers[k].storeTime > storeTime) {
        const componentRefComponentRef<any> = (this.handlers[k].handle as any).componentRef;
        if (componentRef) {
          componentRef.destroy();
        }
        delete this.handlers[k];
      }
    });
  }
}

export interface RootHandler {
  handleDetachedRouteHandle;
  storeTimenumber;
}



Provide this provider (class) in app.module.ts
This is my app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';
import { RouteReuseStrategy } from '@angular/router';
import { CustomRouteReuseStrategy } from './services/custom-route-reuse-strategy';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule
  ],
  providers: [
    {
      provide: RouteReuseStrategy,
      useClass: CustomRouteReuseStrategy
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }




Mark the route you want to detach.
When navigating away from product list I want to store the product list route so I mark it as detachable (shouldDetach: true)

import { ProductDetailComponent } from './pages/product-detail/product-detail.component';
import { NgModule } from '@angular/core';
import { RouterModuleRoutes } from '@angular/router';
import { ProductListComponent } from './pages/product-list/product-list.component';

const routesRoutes = [
  {
    path: "",
    component: ProductListComponent,
    data: {
      shouldDetach: true
    }
  },
  {
    path: ":id",
    component: ProductDetailComponent
  }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class ProductRoutingModule { }



That's it, now serve your app. turn on/off RouteReuseStrategy provider to see the differences.

Thank you for reading, if you find my post helpful please subscribe to my youtube channel for more guides and give me the motivation to keep going.

Comments

Popular posts from this blog

SEO with Angular, add title, meta keywords, a must-do thing for your website

  For those who don't know what SEO is, SEO stands for Search Engine Optimization, it helps people discover your site through search engines like Google, Bing, ... Take a look at the below picture: To let search engines discover your site, you must let search engines know your site, for google you can use Google Search Console  but that's another topic, I assume search engines already know your site. The 3 most important factors are URL, title tag, and meta tag (description, keyword,...). Search engines will look for those 3 things (and many other factors) to compare with the search query and decide whether your site is relevant or not. Url Try to use meaningful URLs. For example, instead of using a URL like https://yoursite.com/products/1 use https://yoursite.com/products/iphone or even better https://yoursite.com/iphone . You can achieve this by configuring your rooting modules. To make slug URLs you can strip accents your product names and join them with a hyphen (-). For e...

Create and deploy multi language (i18n) website with Angular 8+

This guide should work with Angular 8+.  In  this post: 1. Create an angular web project. 2. Set up i18n and translate. 3. Deploy the website on Windows using IIS. In this guide, I am using 2 languages, the setup for more than 2 languages should be similar. You can find the code of this guide in my github . 1. Create an angular web project. To create an angular app, in command  prompt type: ng new multiLanguageApp Open "app.component.html" delete the default code and paste this code : < div >    < a   href = "/" > de </ a > &nbsp;    < a   href = "/en" > en </ a > </ div > < div > Danke! </ div > Run "ng serve" to serve your app. Go to localhost:4200 you should see: I am creating an app with German as the default language and English as the second language. "Danke" is a word in German means "Thank you". When we click "de" or "en" in the UI to switch langu...