意味がわからないタイトルですみません。
なんと書けば伝わるのか、なかなかわからないです。 :sweat:

やりたかったこと

  • メニューが入ったツールバーを表示したい
  • アプリケーション全体で共通のメニューをルートコンポーネントで定義したい
  • ルーティング先のコンポーネントで、コンポーネント専用のメニューを定義したい

完成形はこんな感じです。

Angular-context-menubar.mov.gif

Item1コンポーネントで、Item 1 Menuを定義しています。
Item2コンポーネントで、Item 2 Menuを定義しています。
それらをルートコンポーネント内で定義しているツールバー内に表示しています。

やったこと

  • ルートコンポーネントのhtml内に*ngTemplateOutletを置き、外部定義されたテンプレートを表示させるようにしました。
  • ルートのactivateイベントを拾い、ルーティング先コンポーネントからhtmlを取得して上記のテンプレートに入れるようにしました。
  • ルーティング先コンポーネントのhtml内でng-templateを用いてテンプレートを定義しました。
  • ルーティング先コンポーネントで、テンプレートを公開しました。

実装

実際に作ったものたちです。
Angular Materialを利用しています。

ルートコンポーネント

ルートコンポーネントのhtml内に*ngTemplateOutletを置き、外部定義されたテンプレートを表示するようにします。

app.component.html

      <ng-container *ngTemplateOutlet="additionalMenu"></ng-container>

additionalMenuTemplateRef<any>型です。

app.component.ts

export class AppComponent {
  additionalMenu: TemplateRef<any>;
}

ルートのactivateイベントを拾うようにします。

app.component.html

    <router-outlet
      #routerOutlet
      (activate)="onActivate($event)"
    ></router-outlet>

activateイベントの引数は、コンポーネントのインスタンスそのものになります。
ここでテンプレートを取得して*ngTemplateOutletが参照する変数へ代入しています。

app.component.ts

  onActivate(event): void {
    if (isWithTemplate(event)) {
      this.additionalMenu = event.template;
    } else {
      this.additionalMenu = undefined;
    }
  }

ここで使っているisWithTemplateは型ガードというもので、型判定とキャストを同時にやります。

app.component.ts

const isWithTemplate = (object: any): object is WithTemplate =>
  !!object.template;

WithTemplateというのは作成したインターフェースです。TemplateRef<any>を公開するコンポーネントであることを意味させます。

with-template.ts

import { TemplateRef } from '@angular/core';

export interface WithTemplate {
  template: TemplateRef<any>;
}

ルーティング先コンポーネント

Item1ComponentItem2Componentを作りました。

html内でng-templateを用いてテンプレートを定義しました。

item1.component.ts

   <ng-template #template1>
      <button mat-button [matMenuTriggerFor]="menu">Item 1 Menu</button>
      <mat-menu #menu>
        <button mat-menu-item (click)="countUp()">Count Up</button>
        <button mat-menu-item (click)="countDown()">Count Down</button>
      </mat-menu>
    </ng-template>

テンプレートを公開していることを意味するWithTemplateを実装し、公開しました。

export class Item1Component implements WithTemplate {  
  @ViewChild('template1')  
  template: TemplateRef<any>;  
}  

Item2Componentも同じような内容なので省略します。

ソース全体

こちらに置きました。

https://github.com/sengokyu/ex-ng-template-outlet

ちなみに、テストは失敗します。

感想

一応動きましたが、Angularのお作法的によいのかどうか気になります。