実際、MatDialog 内の ngComponentOutlet 埋め込みコンポーネントでさらに問題が発生しています。しかし、ここから始めましょう。
私が構築しているもの
MatDialog 内に任意のコンポーネントを表示したい。私は方法を見つけましたが、Angular 9 (私が書いた例を見つけたバージョン) で動作しますが、Angular 11 (私のプロジェクトが基づいているバージョン) や Angular 13 (@latest )。
観察
- 内部 HTML に a が含まれて
<button (click)="close()">Close</button>
いてボタンをクリックすると、内部コンポーネントのclose()
メソッドがトリガーされない - の代わりにイベント
close()
にバインドすると、メソッドがトリガーされます。おそらく他のイベントでも機能しますが、(mousedown)
(click)
(click)
- ボタンをクリックすると、代わりに内部コンポーネントがリロードされます (例のコンソール ログを参照)
- ダイアログのどこかをクリックすると、内部コンポーネントがリロードされます (例のコンソール ログを参照)。Angular 9 では発生しません
Angular 9 にはこの問題はありません。以下の両方の例でまったく同じアプリ コードを使用しています (両方のプロジェクトは で作成されng new
、異なるng
バージョンを使用しています)。
再現例
( stackblitz は病気です。500 秒をくしゃみした場合は、数回再試行してください。おそらく covid... )
- Angular 9 の例では、MatDialog は期待どおりに機能します
- Angular 11 の例では、MatDialog が期待どおりに機能しません
- Angular 13 (@latest) を試しましたが、問題は解決しません
質問
- なぜこうなった?
- どうすればこれを回避できますか?
RAW ファイル FFR
app.module.ts
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {AppComponent} from './app.component';
import {MatDialogModule} from '@angular/material/dialog';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {BaseDialogComponent, SampleInnerComponent} from './my-dialog.service';
@NgModule({
declarations: [
AppComponent,
BaseDialogComponent, SampleInnerComponent
],
imports: [
BrowserModule,
MatDialogModule, BrowserAnimationsModule
],
exports: [BaseDialogComponent, SampleInnerComponent],
providers: [BaseDialogComponent, SampleInnerComponent],
bootstrap: [AppComponent],
entryComponents: [BaseDialogComponent, SampleInnerComponent]
})
export class AppModule { }
app.component.ts
import {Component} from '@angular/core';
import {MyDialogService} from './my-dialog.service';
import {MatDialogRef} from '@angular/material/dialog';
@Component({
selector: 'app-root',
template: `
<button (click)="toggle()">TOGGLE</button>
`,
})
export class AppComponent {
title = 'repro-broken';
private dialogRef: MatDialogRef<any>;
constructor(private dialogService: MyDialogService) {
}
toggle(): void {
if (this.dialogRef) {
this.dialogRef.close(undefined);
this.dialogRef = undefined;
} else {
this.dialogRef = this.dialogService.open();
}
}
}
my-dialog.service.ts
import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material/dialog';
import {Component, Inject, Injectable, Injector} from '@angular/core';
import {ReplaySubject} from 'rxjs';
import {tap} from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class MyDialogService {
constructor(private dialog: MatDialog) {
}
open(): MatDialogRef<any> {
const innerComp = new InjectedDialogRef();
const dialogRef = this.dialog.open(BaseDialogComponent, {
// width: '',
// height: '',
// closeOnNavigation: false,
// disableClose: true,
// backdropClass: [],
// hasBackdrop: false,
data: {component: SampleInnerComponent, data: innerComp}
});
innerComp.dialog$.next(dialogRef);
return dialogRef;
}
}
@Injectable()
export class InjectedDialogRef {
dialog$ = new ReplaySubject<MatDialogRef<any>>(1);
}
@Component({
selector: 'app-dialog-sample',
template: `
<div (mousedown)="stuff()">Dialog Inner Component</div>
<button (click)="close()">Close</button>
<!-- <button (click)="stuff()">Stuff</button>-->
`,
})
export class SampleInnerComponent {
public dialog: MatDialogRef<any>;
constructor(private inj: InjectedDialogRef) {
inj.dialog$
.pipe(tap(evt => console.log('Got a dialog', evt)))
.subscribe(dialog => this.dialog = dialog);
}
close(): void {
console.log('Closing the dialog', this.dialog);
this.dialog.close(undefined);
}
stuff(): void {
console.log('Doing stuff');
}
}
@Component({
selector: 'app-dialog-base',
template: `
<h2 mat-dialog-title>MyTitle</h2>
<div mat-dialog-content>
<ng-container *ngComponentOutlet="inner.component; injector:createInjector(inner.data)"></ng-container>
</div>
`,
})
export class BaseDialogComponent {
constructor(
@Inject(MAT_DIALOG_DATA) public inner: any,
private inj: Injector) {
console.log('Opening base dialog');
}
createInjector(inj: InjectedDialogRef): Injector {
return Injector.create({
providers: [{provide: InjectedDialogRef, useValue: inj}],
parent: this.inj
});
}
}