[Angular] 文脈(ルーティングしたコンポーネント)に応じたメニューバーを表示したい
意味がわからないタイトルですみません。
なんと書けば伝わるのか、なかなかわからないです。
やりたかったこと
- メニューが入ったツールバーを表示したい
- アプリケーション全体で共通のメニューをルートコンポーネントで定義したい
- ルーティング先のコンポーネントで、コンポーネント専用のメニューを定義したい
完成形はこんな感じです。
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>
additionalMenu
はTemplateRef<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>;
}
ルーティング先コンポーネント
Item1Component
とItem2Component
を作りました。
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のお作法的によいのかどうか気になります。