angularangular-routingangular-routerangular-dynamic-componentsng-component-outlet

Override ngComponentTemplate from root component


I am trying to wrap my head around the following issue. In the root-level app.component you have the following structure:

<div class="container">
  <sidebar  />

  <menu-bar />

  <div class="h-full w-full overflow-auto">
    <main class="overflow-auto">
      <router-outlet></router-outlet>
    </main>
  </div>
</div>

The menu-bar is only used in some of the routes and even might change. Therefore I don't want to provide a specific implementation, but more or less:

<div class="container">
  <sidebar  />

  <ng-container *ngComponentOutlet="..." />

  <div class="h-full w-full overflow-auto">
    <main class="overflow-auto">
      <router-outlet></router-outlet>
    </main>
  </div>
</div>

This outlet should be provided by a child component (indirectly). The child component depends on the current route.

ASP.NET Blazor for example has a concept called SectionOutlet which describes exactly what I want:

Here the app.component.html:

<div class="container">
  <sidebar  />

  <section-outlet SectionName="menu-bar" />

  <div class="h-full w-full overflow-auto">
    <main class="overflow-auto">
      <router-outlet></router-outlet>
    </main>
  </div>
</div>

Now when I have a process page, this page can then easily provide the content: process-page.component.html:

<section-content SectionName="menu-bar">
 <my-real-menu/>
</section>

<OtherContent/>

What is the idiomatic way of doing this in Angular?


Solution

  • Here are two ways to do this.

    You can write a switchStatement or if statement that returns the correct menu component based on the route used.

    private sub: Subscription = new Subscription();
    menuComp: any;
    getMenu() {
         if(this.router.url.includes('/a-route') {
              this.menuComp = AMenuComponent
         } else if(this.router.url.includes('/b-route') {
              this.menuComp = BMenuComponent
         }
    }
    

    Then on router events navigation you trigger the logic to rerun.

    ngOnInit() {
      this.sub.add(
        this.router.events.subscribe(value => {
            if(value instanceof NavigationEnd)    
              this.getMenu();
            })
      );
    }
    
    ngOnDestroy() {
        this.sub.unsubscribe();
    }
    

    Finally use this property to dynamically create the menu.

    <div class="container">
      <sidebar  />
    
      <ng-container *ngComponentOutlet="menuComp" />
    
      <div class="h-full w-full overflow-auto">
        <main class="overflow-auto">
          <router-outlet></router-outlet>
        </main>
      </div>
    </div>
    

    Another way is to use this same approach is to specify the component on the route data property.

    export const routes: Routes = [
        { path: 'a-route', component: AComponent, data: { menu: AMenuComponent } },
    ];
    

    The on the component root, subscribe to the data changes on the first child, then you can access this component and render using component outlet.

    private sub: Subscription = new Subscription();
    menuComp: any;
    
    ngOnInit() {
      this.sub.add(
        this.activatedRoute.firstChild.data.subscribe((data: any) => {
            this.menuComp = data.menu;
      );
    }
    
    ngOnDestroy() {
        this.sub.unsubscribe();
    }
    

    Same html for this as above method.