Angular Material Dialog基于工厂模式的DRY模态封装问题求解
方案可行性判定
这套基于工厂模式的模态抽象层完全可以依托Angular Material Dialog实现,能够全部覆盖提出的四项核心设计目标,不存在平台能力层面的阻碍。
现存故障根因
当前自定义标题、自定义动作按钮无法渲染的核心原因有两点:
- 服务层打开弹窗的逻辑存在顺序错误:当前实现直接将传入的业务组件作为MatDialog的根渲染组件,完全绕开了预定义的共享
ModalComponent容器,弹窗打开后直接渲染业务内容,根本没有加载带标题栏、操作栏结构的共享容器,所有针对容器的配置绑定自然全部失效。 - 组件传值方式不符合Angular Dialog的传值规则:当前共享容器组件依赖
@Input()接收Modal实例配置,但MatDialog打开的组件不会通过普通组件输入属性传值,所有配置需要通过MAT_DIALOG_DATA依赖注入的方式获取,这也导致哪怕容器被加载,也读不到传入的标题、模板配置。
注:当前业务数据可以正常接收,是因为MatDialog本身的
data配置会直接传给根打开的业务组件,和容器层的逻辑故障没有关联。
分步修复方案
1. 修正Modal服务层的弹窗启动逻辑
调整createModal方法的逻辑:将共享ModalComponent作为MatDialog的根渲染组件,把所有Modal配置(基础弹窗参数、展示选项、动作模板、待渲染的业务组件)统一通过MatDialogConfig.data透传给共享容器。
修正后的服务层代码:
import { Injectable } from "@angular/core"; import { MatDialog, MatDialogConfig } from "@angular/material/dialog"; import { Modal } from "../models/modal"; import { ModalComponent } from "../../components/modal/modal.component"; @Injectable() export class ModalService { constructor(public dialog: MatDialog) {} public createModal(modal?: Modal): any { // 合并原弹窗基础配置,把所有自定义配置透传到共享容器 const dialogConfig: MatDialogConfig = { ...modal.dialog, data: { ...modal.dialog.data, // 保留业务层传入的原始数据 modalOptions: modal.options, modalTemplates: modal.templates, renderTargetComponent: modal.component } }; // 打开共享容器组件,而非直接渲染业务组件 return this.dialog.open(ModalComponent, dialogConfig); } }
2. 修正共享容器组件的接收与渲染逻辑
调整ModalComponent的实现:
- 移除原有依赖
@Input()接收Modal实例的逻辑,改为通过MAT_DIALOG_DATA注入获取所有传入配置 - 增加动态组件渲染逻辑,将待展示的业务组件挂载到弹窗内容区的指定锚点
- 调整模板内的取值路径,从注入的配置对象中读取标题、动作模板
核心调整后的组件代码片段:
import { Component, ComponentFactoryResolver, OnInit, Inject, ViewChild, TemplateRef } from "@angular/core"; import { MAT_DIALOG_DATA } from "@angular/material/dialog"; @Component({ selector: "app-modal", styleUrls: ["./modal.component.css"], template: ` <div> <header> <h1 mat-dialog-title>{{ injectedData.modalOptions.title }}</h1> <button mat-icon-button mat-dialog-close="true"> <i class="material-icons">clear</i> </button> </header> <mat-dialog-content> <!-- 业务组件挂载锚点 --> <ng-template #contentAnchor></ng-template> </mat-dialog-content> <div *ngIf="injectedData.modalOptions.footer"> <footer class="flex justify-between w-full"> <div class="modalFooterLeft"> <mat-dialog-actions> <ng-container [ngTemplateOutlet]="injectedData.modalTemplates.actionsLeft" ></ng-container> </mat-dialog-actions> </div> <div class="modalFooterRight"> <mat-dialog-actions> <ng-container [ngTemplateOutlet]="injectedData.modalTemplates.actionsRight" ></ng-container> <button *ngIf="injectedData.modalOptions.cancel" mat-button mat-dialog-close cdkFocusInitial > Cancel </button> <button *ngIf="injectedData.modalOptions.close" mat-button mat-dialog-close > Close </button> </mat-dialog-actions> </div> </footer> </div> </div> ` }) export class ModalComponent implements OnInit { // 挂载内容区锚点的引用 @ViewChild('contentAnchor', { static: true }) contentAnchor: TemplateRef<any>; constructor( // 注入Dialog传入的所有配置 @Inject(MAT_DIALOG_DATA) public injectedData: any, private componentFactoryResolver: ComponentFactoryResolver ) {} ngOnInit() { // 动态渲染业务组件到内容区 const componentFactory = this.componentFactoryResolver.resolveComponentFactory( this.injectedData.renderTargetComponent ); // 获取锚点的视图容器引用 const viewContainerRef = this.contentAnchor.createEmbeddedView(null).containerRef; viewContainerRef.clear(); // 创建业务组件实例 viewContainerRef.createComponent(componentFactory); } }
注:Angular 13+版本可直接通过
viewContainerRef.createComponent()内置的工厂解析能力简化上述逻辑,无需手动引入ComponentFactoryResolver,可根据项目版本适配写法。
3. 业务侧模板传递的校验要点
业务组件里定义的自定义动作模板,需要通过公开的类属性绑定对应的TemplateRef引用,才能让外层Modal实例拿到正确的模板对象:
// 业务组件类的对应实现 @ViewChild('actionsRight') modalFooterRightRef: TemplateRef<any>; @ViewChild('actionsLeft') modalFooterLeftRef: TemplateRef<any>;
完成这步绑定后,调用时传入的模板引用就能被共享容器的ngTemplateOutlet正确识别并渲染。
内容的提问来源于stack exchange,提问作者CT100
相关产品推荐
相关产品推荐

