18

I tried to use custom reuse strategy in my angular2 project, but I found it doesn't work with lazy module loading. Anyone who know about this? My project is angular 2.6.4

reuse-strategy.ts

import {RouteReuseStrategy, ActivatedRouteSnapshot, DetachedRouteHandle} from "@angular/router";

export class CustomReuseStrategy implements RouteReuseStrategy {

    handlers: {[key: string]: DetachedRouteHandle} = {};

    shouldDetach(route: ActivatedRouteSnapshot): boolean {
        console.debug('CustomReuseStrategy:shouldDetach', route);
        return true;
    }

    store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
        console.debug('CustomReuseStrategy:store', route, handle);
        this.handlers[route.routeConfig.path] = handle;
    }

    shouldAttach(route: ActivatedRouteSnapshot): boolean {
        console.debug('CustomReuseStrategy:shouldAttach', route);
        return !!route.routeConfig && !!this.handlers[route.routeConfig.path];
    }

    retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
        console.debug('CustomReuseStrategy:retrieve', route);
        if (!route.routeConfig) return null;
        return this.handlers[route.routeConfig.path];
    }

    shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
        console.debug('CustomReuseStrategy:shouldReuseRoute', future, curr);
        return future.routeConfig === curr.routeConfig;
    }

}

app.module.ts

const appRoutes: Routes = [
  { path: 'crisis-center', component: CrisisListComponent },
  { path: 'heroes', loadChildren: 'app/hero-list.module#HeroListModule' },
  { path: '',   redirectTo: '/crisis-center', pathMatch: 'full' }
];
@NgModule({
  imports: [ ... ],
  declarations: [ ... ],
  providers:[
    {provide: RouteReuseStrategy, useClass: CustomReuseStrategy}
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

and I put <input> to both component and I tested it.

the value of input in CrisisListComponent is stored but, the value of HeroListComponent lazy-loaded is not preserved.

I don't know it's not supported yet. Thank you for helping me.

Seungwon
  • 276
  • 3
  • 10

5 Answers5

18

RouteReuseStrategy does work with LazyLoaded components.

The problem here is that you're using route.routeConfig.path as the key to store and retrieve the Handles.

I don't know why, but with LazyLoaded modules, route.routeConfig.path is empty when executing shouldAttach

The solution I use is to define a custom key in my routes, like:

{ path: '...', loadChildren: '...module#...Module', data: { key: 'custom_key' } }

This key value can be easily accessed in the ActivatedRouteSnapshot, like:

route.data.key

With this key you can store and retrieve the handles correctly.

msanford
  • 11,803
  • 11
  • 66
  • 93
Gauss
  • 1,108
  • 14
  • 13
5

Use this one. It use component name as the key to store and retrieve the Handles.

import {ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy} from '@angular/router';

export class CustomReuseStrategy implements RouteReuseStrategy {


  handlers: { [key: string]: DetachedRouteHandle } = {};


  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    return true;
  }

  store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
    this.handlers[this.getKey(route)] = handle;
  }

  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    return !!route.routeConfig && !!this.handlers[this.getKey(route)];
  }

  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
    if (!route.routeConfig) {
      return null;
    }
    return this.handlers[this.getKey(route)];
  }

  shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    return curr.routeConfig === future.routeConfig;
  }

  private getKey(route: ActivatedRouteSnapshot) {
    let key: string;
    if (route.component) {
      key = route.component['name'];
    } else {
      key = route.firstChild.component['name'];
    }
    return key;
  }

}
3

To make it work you should take into account the full path instead of simple route.routeConfig.path as most articles suggests. For example:

private getKey(route: ActivatedRouteSnapshot): string {
    return route.pathFromRoot
        .map((el: ActivatedRouteSnapshot) => el.routeConfig ? el.routeConfig.path : '')
        .filter(str => str.length > 0)
        .join('');
}

store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
    this.handlers[this.getKey(route)] = handle;
}
Eduard
  • 1,464
  • 15
  • 19
2

use this ReuseStrategy

import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router';
export class CustomReuseStrategy implements RouteReuseStrategy {

  private handlers: {[key: string]: DetachedRouteHandle} = {};


  constructor() {

  }

  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    return true;
  }

  store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
    this.handlers[route.url.join("/") || route.parent.url.join("/")] = handle;
  }

  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    return !!this.handlers[route.url.join("/") || route.parent.url.join("/")];
  }

  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
    return this.handlers[route.url.join("/") || route.parent.url.join("/")];
  }

  shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    return future.routeConfig === curr.routeConfig;
  }

}
  • It works for child routes with lazy loading as well, thanks! – VIshal Jain Oct 11 '18 at 10:16
  • It doesn't clear the handlers on logout and I get snapshots of "/dashboard" route on homepage. Is there any way to solve this? – VIshal Jain Oct 12 '18 at 05:22
  • @VIshalJain, without seeing your routing structure, it's hard to diagnose what's going on. Did you have more than one '/dashboard' subpath within your application? That's a potential pitfall I see with the above code. As for handlers (DetachedRouteHandle s) being cleared on logout: from the router you can access the route reuse strategy, and you can cast it to the type of your custom reuse strategy and call a method you've added to clear the handlers array during logout. Since handles are opaque, not sure how to force ngOnDestroy(). My advice: execute a browser refresh at end of logout. – jcairney Feb 05 '19 at 16:47
  • Actually I tried this strategy briefly myself and it seems to be in danger of storing the handle for ` // lazy-routing.module.ts ... { path: '', component: LazyComponent, } ` under the same key as its parent in the route tree `// app-routing.module.ts ... { path: 'lazy_route', loadChildren: 'app/+lazy/lazy.module#LazyModule', } ` which causes the `Error: Cannot reattach ActivatedRouteSnapshot created from a different route`, which I think OP and future readers would like to avoid. Could someone else confirm the same? – jcairney Feb 05 '19 at 18:08
1

use this custom Reuse Strategy file for lazy module loading

import { ActivatedRouteSnapshot, RouteReuseStrategy, DetachedRouteHandle } from '@angular/router';

/** Interface for object which can store both:
 * An ActivatedRouteSnapshot, which is useful for determining whether or not you should attach a route (see this.shouldAttach)
 * A DetachedRouteHandle, which is offered up by this.retrieve, in the case that you do want to attach the stored route
 */
interface RouteStorageObject {
    snapshot: ActivatedRouteSnapshot;
    handle: DetachedRouteHandle;
}

export class CustomReuseStrategy implements RouteReuseStrategy {

    handlers: {[key: string]: DetachedRouteHandle} = {};

    shouldDetach(route: ActivatedRouteSnapshot): boolean {
        console.debug('CustomReuseStrategy:shouldDetach', route);
        return !!route.data && !!(route.data as any).shouldDetach;
    }

    store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
        console.debug('CustomReuseStrategy:store', route, handle);
        this.handlers[route.data['key']]= handle;
    }

    shouldAttach(route: ActivatedRouteSnapshot): boolean {
        console.debug('CustomReuseStrategy:shouldAttach', route);
        return !!route.data && !!this.handlers[route.data['key']];
    }

    retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
        console.debug('CustomReuseStrategy:retrieve', route);
        if (!route.data) return null;
        return this.handlers[route.data['key']];
    }

    shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
        console.debug('CustomReuseStrategy:shouldReuseRoute', future, curr);
        return future.data === curr.data;
    }

}