method. 适用于 Angular 9-6ren"> method. 适用于 Angular 9-事实上,我对 MatDialog 内的 ngComponentOutlet 嵌入式组件有更多问题。但让我们从这里开始吧。 我正在构建什么 我想在 MatDialog 中显示任意组件。我找到了一种方法,-6ren">
gpt4 book ai didi

Angular 11+ MatDialog : inner component's (ngComponentOutlet) html does not trigger

转载 作者:行者123 更新时间:2023-12-05 04:35:38 28 4
gpt4 key购买 nike

事实上,我对 MatDialog 内的 ngComponentOutlet 嵌入式组件有更多问题。但让我们从这里开始吧。

我正在构建什么

我想在 MatDialog 中显示任意组件。我找到了一种方法,但是虽然它适用于 Angular 9(我找到了一个编写示例的版本),但它不适用于 Angular 11(我的项目所基于的版本)或 Angular 13(@latest ).

观察

  • 当内部 HTML 包含 <button (click)="close()">Close</button> 时然后我点击按钮,内部组件的 close()方法没有被触发
  • 它会触发 close()方法,如果我将它绑定(bind)到 (mousedown)事件而不是 (click) ;可能与其他事件一起使用,但 (click)一个
  • 当我点击按钮时,内部组件会重新加载(请参阅示例中的控制台日志)
  • 当我点击对话框中的任意位置时,内部组件会重新加载(请参阅示例中的控制台日志);在 Angular 9 中不会发生

Angular 9 没有这个问题。我在下面的两个示例中使用了完全相同的应用程序代码(两个项目都是使用 ng new 创建的,使用不同的 ng 版本)。

重现示例

(stackblitz 病了,如果它打喷嚏 500 秒,请重试几次。可能是 covid...)

Broken example (Angular 11)

Working example (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();
}
}
}

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

最佳答案

BaseDialogComponent 模板中删除 createInjector(inner.data) 方法调用。

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

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

Stackblitz


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

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

在 Angular Material v11 中,CDK overlay 在 capture 阶段在文档 body 上添加了一个 click 事件监听器。因此,每当您单击时,甚至在与按钮关联的单击监听器有机会执行之前就会触发更改检测,这又会导致重新呈现 View ,因为 createInjector() 方法总是返回一个调用时的新 Injector 实例。

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

when I click anywhere on the dialog, the inner component is reloaded (see console logs in examples); does not happen in Angular 9

click event listener in Angular Material v11

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

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

// AppComponent
constructor(private dialogService: MyDialogService) {
document.body.addEventListener('click', () => console.log('clicked'), true);
}

关于Angular 11+ MatDialog : inner component's (ngComponentOutlet) html does not trigger <button (click) ="close()"> method. 适用于 Angular 9,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71002756/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com