TypeScript
angular
angularMaterial

Angular5とAngular Materialで「クリックしたらダイアログ表示」ディレクティブを作った話

やりたかったこと

  • <a>なり<button>なりを押すと「未実装」ってアラートを出したい
  • action="onClick()"でやってもいいけど、いちいちonClick設定するのダルい
  • window.alert('hoge')とかでもええけど、どうせならAngular MaterialのDialogを使いたい

考えた解決法

  • notImplementedアトリビュートを付けた要素はクリック時にアラートが出るようにしよう
  • んでそのアラートをMatDialogで表示させればいいや

構成

  • view.module 画面用モジュール
    • dashboard.component ダッシュボード用コンポーネント
    • view-parts.module 部品群(使い回しするパーツ)を詰め込むモジュール
    • not-implemeted.directive 未実装を示すアトリビュートとなるディレクティブ
      • NotImplementedDirective アトリビュートそのものになるディレクティブ
      • NotImplementedDialogComponent 上記ディレクティブが生成するコンポーネント

コード

view.module.ts
import { ViewPartsModule } from './view-parts/view-parts.module';
import { DashboardComponent } from './dashboard/dashboard.component';
  ()
@NgModule({
  ()
  imports: [
    ViewPartsModule
  ],
  ()
  declarations: [DashboardComponent],
  ()

ViewPartsModuleのインポートだけ忘れないようにしておきましょう。
ng g m view-parts -m viewってすればそれも自動で挿入されるはず。

dashboard.html
<a myAppNotImplemented text="ほげ">
  未実装ななにかへのリンク
</a>

tsファイルはng g cで生成したものから変更してないので省きます。
myAppはプロジェクト作成時に付けたプレフィクスに応じて変わります。
textに設定した値がダイアログに表示されるようにしてみたけど、「未実装です」表示がやりたいくらいの目的なら不要でしたね……。

view-parts.module.ts
//いちいち個別にモジュールをimportするのが面倒になった
import * as material from '@angular/material';

//今回作ったディレクティブ
//ng g d view/view-parts/not-implemented -m view/view-parts
import { NotImplementedDirective, NotImplementedDialogComponent } from './not-implemented/not-implemented.directive';

@NgModule({
  imports: [
    //NotImplementedDirectiveのDIで必要
    material.MatDialogModule
  ],
  ()
  //動的に生成されるコンポーネントにはこれが必要。
  //ダイアログは動的に生成されるコンポーネントなので必要。
  //ないとダイアログ表示時点でエラー吐く。
  //とはいえ忘れてても「entryComponents忘れてへん?」ってエラーメッセージが出るので有情。
  entryComponents: [NotImplementedDialogComponent],
  ()
  declarations: [NotImplementedDirective, NotImplementedDialogComponent],
  //exportsはngコマンドでは自動で記述されない。
  //なくても別にエラーを吐いたりしないので注意。
  exports: [ NotImplementedDirective ]
})
export class ViewPartsModule { }

作ったDirectiveをexportsに書き忘れてたせいでハマってました。

他の記述忘れはエラー吐くんですが、こいつは忘れてもhtmlパースエラーとか吐いてくれません
無意味なアトリビュートとして普通に無視されるだけです。

not-implemented.directive.ts
import { Component, Directive, HostListener, Inject, Input, ElementRef } from '@angular/core';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material';

@Directive({
    selector: '[myAppNotImplemented]'
})

export class NotImplementedDirective {
  constructor(public dialog: MatDialog) { }

  // ダイアログのテキストを変えようと思った
  // しかし今回の目的の「未実装です」表示くらいなら別に必要がなかった かなしみ
  @Input() text: string;

  //HostListenerには他にもmouseenterとか渡せるようです
  //一覧どっかにないんかな
  @HostListener('click') onclick(): void {
    this.openDialog();
  }

  openDialog(): void {
    // これでダイアログが開きます
    // dataオブジェクトに値を突っ込んで渡すことで、実際に表示されたダイアログに値を渡せます
    const dialogRef = this.dialog.open(NotImplementedDialogComponent, {data:{text:this.text}});
    // おまけ
    // dialogRef.afterClosed().subscribe(result => なんかしらの処理)
    // 表示されるダイアログにmat-dialog-closeアトリビュートを付けておくと、resultにそのデータが入って戻ってきます
   //下記のmat-dialog-closeにtrueを指定してるので、閉じるボタンで閉じたらtrueが戻ってなんかしら処理できます
    //
  }
}

@Component({
  selector: 'bookchain-not-implemented-dialog',
  template: `
  <h2 mat-dialog-title>ダイアログ</h2>
  <mat-dialog-content>
    <p>{{data.text}}</p>
  </mat-dialog-content>
  <mat-dialog-actions>
    <button mat-button [mat-dialog-close]="true">閉じる</button>
  </mat-dialog-actions>
  `
})

export class NotImplementedDialogComponent {
  // dataとして、上のdialog.openで渡したオブジェクトが渡されます
  // 表示したいテキストを切り替えたいときとか用
  constructor(
    public dialogRef: MatDialogRef<NotImplementedDialogComponent>,
    @Inject(MAT_DIALOG_DATA) public data: any
  ) {}

}

詰まった

ViewPartsModuleのexportsに作ったディレクティブ追加してなかったもんだからウンともスンとも言わなかった。
エラーも吐かないもんだから……。

もうちょっと手軽なやつ

今回だけなら未実装表示するだけだしMatDialogじゃなくてwindow.alertでええやんね