Angular Materialでダイアログ
Angular Materialでダイアログを使う際、少し苦労したので、ここにその時のメモ書きを残しておきます。
dialogの作成手順
ダイアログを使用する際の大まかな流れを先に紹介しておきます。
-
ダイアログを利用したいコンポーネントとは別にダイアログ用にコンポーネントを作成します。これはダイアログを使用したいコンポーネント(tsファイル)のクラスの定義の後に続けて書くことができます。
-
ダイアログ用のテンプレート(html)ファイルを作成します。この時の拡張子を除いたファイル名はコンポーネントに指定したものと同じにします。
-
利用する側のコンポーネント側にダイアログをオープンするコードを記述し、ダイアログへの参照を設定します。この時、ダイアログに任意のデータを渡すことができます。
-
ダイアログからのレスポンスを待機するコードを書きます。この処理ではAngularのデフォルト非同期処理ライブラリであるRxJSを用います。subscribeでダイアログのレスポンスを受け取り、コンポーネントに反映させます。このような仕組みになっているのはAngularでは子コンポーネントから親コンポーネントの値を直接修正することはできないためです。
モジュールの定義
app.module.tsでダイアログモジュールをインポートします。
app.module.ts
...
import { MatDialogModule } from '@angular/material/dialog';
...
imports: [
...
MatDialogModule,
],
...
ダイアログ用コンポーネントの記述
呼び出し元のコンポーネントでMatDialog, MatDialogRef, MAT_DIALOG_DATAをインポートし、メインとなるコンポーネントのクラスの記述が終わった後に、もうひとつダイアログ用のコンポーネントを記述します。
ダイアログ側のコンストラクタに設定するdataはメインのコンポーネントから任意のデータを受け取るための変数で、@InjectによりDI(依存性の注入)をしています。
main.componen.ts
...
import {MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog';
...
@Component({
selector: 'main',
templateUrl: 'main.html',
})
export class Main {
...
}
...
@Component({
selector: 'dialog',
templateUrl: 'dialog.html',
})
export class Dialog {
constructor(
public dialogRef: MatDialogRef<Dialog>,
@Inject(MAT_DIALOG_DATA) public data: string[]) {}
buttonClick(str:string): void {
this.dialogRef.close(str);
}
}
ダイアログ用HTMLファイルの記述
ダイアログ中のテンプレート(HTML)の記述は通常のコンポーネントと同様です。ここでは元のコンポーネントから受け取ったdataという文字列配列からメッセージを補完して表示しています。
clickイベント等も通常通り記載できます。
先のtsファイルのcancelメソッド内の記述にあったように、コンポーネント側からダイアログを閉じたい場合はMatDialogRefから.close()を呼びます、この時close(something)という風に、引数に戻したい値をセットすることもできます。
cdkFocusInitialはデフォルトでのフォーカスを指定しています。
dialog.html
<div mat-dialog-content>
<p>{{data[0]}}</p>
</div>
<div mat-dialog-actions>
<button mat-button (click)="buttonClick('cancel')">{{data[1]}}</button>
<button mat-button (click)="buttonClick('delete') cdkFocusInitial>{{data[2]}}</button>
</div>
呼び出し元のコンポーネントの記述
メイン側となるコンポーネントからダイアログを呼び出す際は記述は次のようにします。メイン側のconstructorで定義したMatDialogのopenメソッドに利用するDialogコンポネント名とパラメーターを渡します。
先ほどダイアログに渡すデータを文字列の配列として定義したので、dataに文字列の配列を渡しています。ダイアログのサイズを指定するwidthやheightなど、事前に用意されているプロパティもあります。
ダイアログからのデータはRxJS経由で戻ってきますので、subscribeで受け取ります。AngularにおけるRxJSについては過去の記事でも触れましたのでよろしければそちらも参考にしてみてください。
main.componen.ts
...
constructor(
public dialog: MatDialog
) { }
...
public opneDialog():void {
const dialogRef = this.dialog.open(ダイアログコンポーネント名, {
width: '250px',
data: ['すべてのデータを削除してもよろしいですか?','キャンセル','削除']
});
dialogRef.afterClosed().subscribe((res)=>{
if (res!=='delete') {
console.log('delete');
} else {
console.log('cancel');
}
});
}
...
mat-dialog-closeディレクティブ
ダイアログのHTMLのbuttonタグの中で、[mat-dialog-close]="true"といったようにmat-dialog-closeディレクティブを使ってスクリプトのMatDialogRefのcloseメソッドと同様のことができるようなのですが、筆者の環境ではメインとなるコンポーネントファイルにダイアログを併記した場合は次のようなコンパイルエラーとなってしまいました。
(click)からのスクリプトで処理をするか、ダイアログ専用のコンポーネントを作成すれば回避できました。
ダイアログ専用のコンポーネントを作成する場合は、ng generate component dialog-sample で作成したコンポーネントに先と同じ内容を記載するだけです。
呼び出し側では、MatDialogのopenでここで作成したコンポーネント名を設定します。
dialog-sample.componen.ts
import { Component, OnInit,Inject } from '@angular/core';
import {MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog';
...
export class DialogSampleComponent implements OnInit {
constructor(
public dialogRef: MatDialogRef<DialogSampleComponent>,
@Inject(MAT_DIALOG_DATA) public data: string[]) {}
ngOnInit(): void {
}
buttonClick(str:string): void {
this.dialogRef.close(str);
}
}
...
ちなみに、Angular9より前のバージョン、もしくはAngular Ivyを利用しない設定をしている場合は、app.module.tsにentryComponentsの項目を追加する必要があるそうです。IvyとはAngularの次世代のコンパイルとパイプラインのレンダリングについてのコードネームで、それ以前のものはViewEngineと呼ばれるそうです。