Help us understand the problem. What is going on with this article?

[Angular] 文脈(ルーティングしたコンポーネント)に応じたメニューバーを表示したい

意味がわからないタイトルですみません。
なんと書けば伝わるのか、なかなかわからないです。 :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を実装し、公開しました。

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

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

ソース全体

こちらに置きました。

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

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

感想

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

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away