8

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.

thorn0
  • 9,362
  • 3
  • 68
  • 96
  • Can you provide a minimal repo with your patch? Will see if can do something more non-hackier – Tarun Lalwani Feb 15 '18 at 18:15
  • If you are using the same component over and over, why are you using routerLinks? Can't you just stay on the NavComponent and update the information? – Scuba Kay Feb 16 '18 at 10:53
  • https://stackblitz.com/edit/angular-ptzwrm – thorn0 Feb 16 '18 at 12:01
  • @ScubaKay Because I want to reflect the state of this component in the URL. – thorn0 Feb 16 '18 at 12:08
  • @thorn that seems like a good reason – Scuba Kay Feb 16 '18 at 12:08
  • @thorn I think you don't need a Router at all. Remove everything around Router (routing file + routerLink) and use QueryParams. – Gilsdav Feb 18 '18 at 21:54
  • @Gilsdav I can see why you suggest this. My app is more complex than this isolated issue. The outlet allows for more flexibility than listening to query parameters. It has nice features like guards. And there will be other components in that outlet too. I just couldn't understand why I need two routes while it looks like one would suffice. The primary outlet doesn't behave like this. – thorn0 Feb 18 '18 at 22:52
  • @thorn ok is it the same if you add something in your path ? { path: 'test', component: NavComponent, outlet: 'nav' } http://example.com/foo/test(nav;bar) – Gilsdav Feb 18 '18 at 22:56
  • @Gilsdav So, I still don't fully understand this `'**'` vs `''` thing, it might even be a bug in Angular, or more probably, just one of the many undocumented small design decisions. However, the solution with `RouteReuseStrategy` seems to be good enough for me. At least it works and the component isn't reloaded. I'm going to accept it even though not everything is clear here for me. – thorn0 Feb 18 '18 at 23:00
  • @thorn '' is when you don't have anything after "foo" and '**' is if you have something (everything else) after "foo". What I have in head is using pathMatch: "prefix" on your path ''. Haven't tried. – Gilsdav Feb 18 '18 at 23:05
  • @Gilsdav I tried that. I even tried to use a custom `matcher`, but it simply isn't called at all for URLs of the type (c). – thorn0 Feb 18 '18 at 23:09
  • Seems like for named outlets, there is a difference between the values: an empty string (b) and `null` (c). Whereas the path of the primary outlet simply can't be `null` as this part of the URL isn't considered optional. For the root of the site, the path is an empty string. – thorn0 Feb 18 '18 at 23:14
  • Ok good to know. Sorry I will not be able to help. I personally avoid using named outlet. – Gilsdav Feb 18 '18 at 23:18

2 Answers2

3

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

David
  • 33,444
  • 11
  • 80
  • 118
0

I mean that what you need is a router-outlet nested. Something like this:

app.component.html: <router-outlet></router-outlet>

feature-area.component.html: <your-navbar-component></your-navbarcomponent> <router-outlet></router-outlet>

child-of-feature-area.component.html: < h1>Hi there!</h1>

your-navbar-component.component.html: < p>some links here...</p>

When you access http://localhost:4200/feature-path/child-feature-path you will get:

some links here...

Hi there!

If you need, I can write some pluker with an example to better explain. But I mean that you are overloading the router with a task that maybe is not to him.

  • Sorry, I don't see how your answer is relevant. Take a look at my example code here to see what I'm trying to achieve: https://stackblitz.com/edit/angular-ptzwrm – thorn0 Feb 16 '18 at 12:05
  • I think that you want reflect in the url the navbar state's. I can use queryParams to achieve this and nested router-outlets to preserver the navbar - without destroying and recreating it every time. – Christian G Fritsch Feb 16 '18 at 14:06
  • So your suggestion is not to use secondary (named) outlets at all, right? But it seems to me they were created exactly for usage like this. – thorn0 Feb 16 '18 at 14:35
  • One example of usage to named outlets where it works pretty well are modals. I'm not saying "don't use secondary routes" - I just think that you can do what you want by using another approach, without overload the router responsibility. Or your question is about decide "the best practices"? – Christian G Fritsch Feb 16 '18 at 15:43