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. 我该如何解决这个问题?

原始文件 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();
    }
  }
}

我的-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

1

摆脱模板中的createInjector(inner.data)方法调用。BaseDialogComponent

而是创建注入器并将其存储在BaseDialogComponent属性中。然后将该属性分配给*ngComponentOutlet注入器。

@Component({
  selector: 'app-dialog-base',
  template: `
    <h2 mat-dialog-title>MyTitle</h2>
    <div mat-dialog-content>

    <!-- Removed createInjector(inner.data) method call and replaced with contentInjector property  -->
      <ng-container *ngComponentOutlet="inner.component; injector:contentInjector"></ng-container>

    </div>
  `,
})
export class BaseDialogComponent implements OnInit {
  contentInjector!: Injector; // Defined property to hold the content injector

  constructor(
    @Inject(MAT_DIALOG_DATA) public inner: any,
    private inj: Injector
  ) {
    console.log('Opening base dialog');
  }

  // Created the injector within ngOnInit
  ngOnInit() {
    this.contentInjector = this.createInjector(this.inner.data);
  }

  createInjector(inj: InjectedDialogRef): Injector {
    return Injector.create({
      providers: [{ provide: InjectedDialogRef, useValue: inj }],
      parent: this.inj,
    });
  }
}

堆栈闪电战


为什么相同的代码在 Angular 9 中有效,但在 Angular 11 及更高版本中无效?

首先,问题(变化的行为)不是由于 Angular 框架中的代码,而是由于 Angular Material 中的一些代码。

在 Angular Material v11 中,CDK 覆盖在捕获阶段click在文档上添加了一个事件侦听器。因此,每当您单击时,甚至在与按钮关联的单击侦听器有机会执行之前就触发了更改检测,这会导致重新渲染视图,因为方法总是在调用时返回一个新的 Injector 实例。bodycreateInjector()

由于同样的原因,您观察到组件被重新加载/渲染的以下行为:

当我单击对话框上的任意位置时,内部组件会重新加载(请参阅示例中的控制台日志);在 Angular 9 中不会发生

Angular Material v11 中的单击事件监听器

Angular Material v9 不包含此click事件侦听器代码,因此与按钮关联的侦听器执行并关闭对话框而不会导致任何问题。叠加层内的点击而不是“关闭”按钮再次没有触发任何更改检测,因此没有发生重新渲染。

您可以通过添加如下监听器在 Angular 9 代码中复制相同的行为:

// AppComponent
constructor(private dialogService: MyDialogService) {
  document.body.addEventListener('click', () => console.log('clicked'), true);
}
于 2022-02-06T08:41:35.183 回答