Search code examples
angularangular-routerangular-auxiliary-routes

Angular 5: how do I route all paths for an outlet to the same component using only 1 route?


My current configuration:

const routes: Routes = [
  { path: '', component: NavComponent, outlet: 'nav' },  // (1)
  { path: '**', component: NavComponent, outlet: 'nav' } // (2)
];

It works. NavComponent is always rendered to the outlet nav. In particular, it works for all the following kinds of URLs:

http://example.com/foo(nav:bar)     // (a) non-empty path in nav   -->  (2)
http://example.com/foo(nav:)        // (b) empty path in nav       -->  (2)
http://example.com/foo              // (c) no nav at all           -->  (1)

Notice that the router matches different routes to these URLs:

  • (1) is used for (c)
  • (2) is used for (a) and (b)

That is why the NavComponent instance is destroyed and recreated every time the location changes say from (c) to (a). And that's something I need to prevent. I need to keep my instance because of its state, animations, etc. As far as I understand, it's possible only if the same route is used for all the URLs, however I can't find a way to do this. If I remove (1), the URLs like (c) stop showing NavComponent in nav. Apparently ** doesn't match such URLs (I'm not sure why though).

You can see it in action here: https://stackblitz.com/edit/angular-ptzwrm

What is the proper solution here?

For now, I'm overriding UrlSerializer to add (nav:) to URLs like (c) before parsing, but it feels like a hack.


Solution

  • Dumb question, but can you not simply modify the URL using location service and stay on the same component (and just change states for your animations)?

    Otherwise, you can implement a custom RouteReuseStrategy to force reusing your component

    import { RouteReuseStrategy } from '@angular/router';
    
    import {ActivatedRouteSnapshot} from '@angular/router';
    import { DetachedRouteHandle } from '@angular/router';
    
    
    /** Use defaults from angular internals, apart from shouldReuseRoute **/
    
    
     export class CustomReuseStrategy implements RouteReuseStrategy {
        shouldDetach(route: ActivatedRouteSnapshot): boolean { return false; }
        store(route: ActivatedRouteSnapshot, detachedTree: DetachedRouteHandle): void {}
        shouldAttach(route: ActivatedRouteSnapshot): boolean { return false; }
        retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle|null { return null; }
    
    
       shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
           let name = future.component && (<any>future.component).name;
    
        return future.routeConfig === curr.routeConfig || name == "NavComponent";
      }
    }
    
    
    @NgModule({
    
      providers: [
    
        {
          provide: RouteReuseStrategy,
          useClass: CustomReuseStrategy
        }]
    })
    export class AppModule { }
    

    Here is your modified stackblitz, which will always reuse your NavComponent

    https://stackblitz.com/edit/angular-tj5nrm?file=app/app.module.ts

    Links

    Route reuse Strategy explained: https://medium.com/@gerasimov.pk/how-to-reuse-rendered-component-in-angular-2-3-with-routereusestrategy-64628e1ca3eb

    Default values for angular router strategy: https://github.com/angular/angular/blob/master/packages/router/src/route_reuse_strategy.ts