LoginSignup
1
2

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-11-25

意味がわからないタイトルですみません。
なんと書けば伝わるのか、なかなかわからないです。 :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も同じような内容なので省略します。

ソース全体

こちらに置きました。

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

感想

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

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2