Angular2: display different toolbar components depending on the main navigation

This is a conceptual question about how to implement the required functionality in the “right” way using Angular 2.

My application has a navigation menu, toolbar, and content area. The latter contains the primary <router-outlet> and displays various views, such as a list and details.

I want to ensure that the toolbar displays different components, depending on the component / view displayed in the content area. For example, a list component requires a search control on the toolbar, and a part component requires a save button.

A) My first attempt was to add another (named) <router-outlet> toolbar and display the toolbar components based on static routes. What's bad about it:

  • The ratio of static content to the toolbar is too weakly related to my taste.
  • The relationship is visible (and changed) in the URL.
  • The output on the toolbar saves this path, even if the user navigates from it.

B) My second attempt was to go without fail to the toolbar components (also using the named toolbar) in the main component of the ngOnInit , which more closely links it. What smells bad:

  • A2
  • A3, to prevent this, I could “clear” the toolbar output on ngOnDestroy , but I did not know how to do this.

C) Giving the router a last chance, since I found out that this type of work:

 const ROUTES: Routes = [ {path: "buildings", children: [ {path: "", component: BuildingListComponent, pathMatch: "full", outlet: "primary"}, {path: "", component: BuildingListToolbarComponent, pathMatch: "full", outlet: "toolbar"}, {path: ":id", component: BuildingDashboardComponent, outlet: "primary"} ]} ]; 

The idea is that the router selects the appropriate output path. But (nooo, it could be that simple), unfortunately, this does not work:

 const ROUTES: Routes = [ {path: "buildings", children: [ {path: "list", component: BuildingListComponent, pathMatch: "full", outlet: "primary"}, {path: "list", component: BuildingListToolbarComponent, pathMatch: "full", outlet: "toolbar"}, {path: ":id", component: BuildingDashboardComponent, outlet: "primary"} ]} ]; 

Explicitly (and possibly by accident) only works with an empty path. Why, oh why?

D) The full strategy is to rework my component hierarchy so that each component of the main view contains a corresponding toolbar and uses multi-line content . Did not try this, but I'm afraid to run into problems with multiple instances of the toolbar.

How sometimes this seems like a common use case, and I wonder how Angular 2 experts can solve it. Any ideas?

+8
angular angular2-template angular2-routing
source share
1 answer

As suggested by Günter Zöchbauer (thanks!), I ended up adding and removing dynamic components in the toolbar. The required toolbar component is specified in the route data attribute and is evaluated by the central component (navbar), which contains the toolbar.
Note that the navbar component does not need to know anything about the toolbar components (which are defined in the feauture modules).
Hope this helps someone.

buildings-routing.module.ts

 const ROUTES: Routes = [ {path: "buildings", children: [ { path: "", component: BuildingListComponent, pathMatch: "full", data: {toolbar: BuildingListToolbarComponent} }, { path: ":id", component: BuildingDashboardComponent, data: {toolbar: BuildingDashboardToolbarComponent} } ]} ]; @NgModule({ imports: [ RouterModule.forChild(ROUTES) ], exports: [ RouterModule ] }) export class BuildingsRoutingModule { } 

navbar.component.html

 <div class="navbar navbar-default navbar-static-top"> <div class="container-fluid"> <form class="navbar-form navbar-right"> <div #toolbarTarget></div> </form> </div> </div> 

navbar.component.ts

 @Component({ selector: 'navbar', templateUrl: './navbar.component.html', styleUrls: ['./navbar.component.scss'] }) export class NavbarComponent implements OnInit, OnDestroy { @ViewChild("toolbarTarget", {read: ViewContainerRef}) toolbarTarget: ViewContainerRef; toolbarComponents: ComponentRef<Component>[] = new Array<ComponentRef<Component>>(); routerEventSubscription: ISubscription; constructor(private router: Router, private componentFactoryResolver: ComponentFactoryResolver) { } ngOnInit(): void { this.routerEventSubscription = this.router.events.subscribe( (event: Event) => { if (event instanceof NavigationEnd) { this.updateToolbarContent(this.router.routerState.snapshot.root); } } ); } ngOnDestroy(): void { this.routerEventSubscription.unsubscribe(); } private updateToolbarContent(snapshot: ActivatedRouteSnapshot): void { this.clearToolbar(); let toolbar: any = (snapshot.data as {toolbar: Type<Component>}).toolbar; if (toolbar instanceof Type) { let factory: ComponentFactory<Component> = this.componentFactoryResolver.resolveComponentFactory(toolbar); let componentRef: ComponentRef<Component> = this.toolbarTarget.createComponent(factory); this.toolbarComponents.push(componentRef); } for (let childSnapshot of snapshot.children) { this.updateToolbarContent(childSnapshot); } } private clearToolbar() { this.toolbarTarget.clear(); for (let toolbarComponent of this.toolbarComponents) { toolbarComponent.destroy(); } } } 

Literature:
https://vsavkin.com/angular-router-understanding-router-state-7b5b95a12eab
https://engineering-game-dev.com/2016/08/19/angular-2-dynamically-injecting-components
Angular 2 dynamic tabs with selected components selected by user

Change page name using Angular 2 new router

+8
source share

All Articles