2

実際、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 11)

作業例 (Angular 9)

  • Angular 9 の例では、MatDialog は期待どおりに機能します
  • Angular 11 の例では、MatDialog が期待どおりに機能しません
  • Angular 13 (@latest) を試しましたが、問題は解決しません

質問

  1. なぜこうなった?
  2. どうすればこれを回避できますか?

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
    });
  }
}

4

1 に答える 1