最近在做一个用户管理模块,需要在表格中点击"编辑"按钮弹出表单弹窗来修改数据。刚开始用
d-modal组件直接写,结果各种问题,后来发现官方推荐用DialogService,这才算解决了。记录一下踩坑过程。
前言
弹窗表单是后台管理系统里最常见的交互模式了。用户列表页面,点击编辑按钮,弹出一个表单弹窗,修改完数据保存,更新列表。听起来简单,但实际做起来还是有不少细节要注意的。
我一开始想当然地直接用 d-modal 组件,结果发现控制显示隐藏、数据回填、表单验证这些地方都挺麻烦的。后来看了官方文档,发现用 DialogService 动态创建弹窗会更简单。这篇文章就记录一下怎么用 DialogService 实现弹窗表单联动。

一、DialogService 的正确打开方式
DevUI 提供了两种弹窗使用方式:一种是直接用 d-modal 组件,另一种是用 DialogService 动态创建。对于表单弹窗这种场景,官方推荐用 DialogService。
首先,需要在 app.config.ts 中提供 DialogService 和它的依赖:
typescript
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideAnimations } from '@angular/platform-browser/animations';
import { DialogService } from 'ng-devui/modal';
import { OverlayContainerRef } from 'ng-devui/overlay-container';
import { DevConfigService } from 'ng-devui/utils';
import { DocumentRef } from 'ng-devui/window-ref';
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideAnimations(),
DialogService,
OverlayContainerRef,
DevConfigService,
DocumentRef,
],
};
这里有个坑:DialogService 依赖 OverlayContainerRef,而 OverlayContainerRef 又依赖 DocumentRef,这些都需要手动提供。如果漏了哪个,运行时会报 NullInjectorError。
二、创建表单组件
表单组件不需要包含 d-modal 标签,只需要表单内容。DialogService 会自动帮你套上弹窗的外壳。
typescript
// user-edit-modal.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { FormModule } from 'ng-devui/form';
import { TextInputModule } from 'ng-devui/text-input';
import { SelectModule } from 'ng-devui/select';
import { RadioModule } from 'ng-devui/radio';
@Component({
selector: 'app-user-edit-modal',
standalone: true,
imports: [
CommonModule,
ReactiveFormsModule,
FormModule,
TextInputModule,
SelectModule,
RadioModule,
],
templateUrl: './user-edit-modal.component.html',
styleUrl: './user-edit-modal.component.scss',
})
export class UserEditModalComponent implements OnInit {
@Input() data: any; // DialogService 会通过 data 传递数据
userForm!: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit(): void {
this.initForm();
// 从 data 中获取数据
if (this.data?.userData) {
this.loadUserData(this.data.userData);
}
}
initForm(): void {
this.userForm = this.fb.group({
name: ['', [Validators.required, Validators.minLength(2)]],
age: [null, [Validators.required, Validators.min(18)]],
gender: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
department: ['', Validators.required],
});
// 监听表单变化,更新按钮状态
this.userForm.valueChanges.subscribe(() => {
if (this.data?.canConfirm) {
this.data.canConfirm(this.userForm.valid);
}
});
}
loadUserData(data: any): void {
// 数据格式转换
const formData = {
name: data.name || '',
age: data.age || null,
gender: data.gender === '男' ? 'male' : 'female',
email: data.email || '',
department: this.getDepartmentKey(data.department),
};
this.userForm.patchValue(formData);
}
onSubmit(): void {
if (this.userForm.valid) {
const submitData = {
...this.userForm.value,
gender: this.userForm.value.gender === 'male' ? '男' : '女',
department: this.departmentOptions.find(
d => d.key === this.userForm.value.department
)?.value || '',
};
// 调用回调函数
if (this.data?.onSave) {
this.data.onSave(submitData);
}
} else {
// 标记所有字段为 touched,显示验证错误
Object.keys(this.userForm.controls).forEach(key => {
this.userForm.get(key)?.markAsTouched();
});
}
}
}
关键点:
- 组件通过
@Input() data接收DialogService传递的数据 - 表单验证通过后,调用
data.onSave回调函数 - 表单变化时,通过
data.canConfirm更新弹窗按钮的禁用状态
三、在表格组件中使用 DialogService
表格组件中注入 DialogService,点击编辑按钮时调用 open 方法:
typescript
// table.component.ts
import { Component } from '@angular/core';
import { DialogService } from 'ng-devui/modal';
import { UserEditModalComponent } from '../user-edit-modal/user-edit-modal.component';
@Component({
selector: 'app-table',
standalone: true,
imports: [DataTableModule, PaginationModule, ButtonModule, CommonModule],
templateUrl: './table.component.html',
})
export class TableComponent {
constructor(private dialogService: DialogService) {}
// 打开编辑弹窗
openEditModal(user: any): void {
const results = this.dialogService.open({
id: 'user-edit-dialog',
width: '600px',
title: '编辑用户',
content: UserEditModalComponent,
backdropCloseable: true,
data: {
userData: user,
onSave: (userData: any) => {
// 更新表格数据
this.updateUserData(user.id, userData);
results.modalInstance.hide();
},
canConfirm: (value: boolean) => {
// 更新保存按钮的禁用状态
results.modalInstance.updateButtonOptions([{ disabled: !value }]);
}
},
buttons: [
{
cssClass: 'primary',
text: '保存',
disabled: true,
handler: ($event: Event) => {
if (results.modalContentInstance?.onSubmit) {
results.modalContentInstance.onSubmit();
}
},
},
{
cssClass: 'common',
text: '取消',
handler: () => {
results.modalInstance.hide();
},
},
],
});
}
}
dialogService.open() 返回的对象包含:
modalInstance:弹窗实例,可以调用hide()关闭弹窗modalContentInstance:表单组件实例,可以调用组件的方法

四、样式优化
弹窗内容区域的样式需要注意,要确保背景透明,和弹窗背景一致:
scss
.modal-content {
padding: 0;
background: transparent !important;
form {
background: transparent !important;
d-form-item {
margin-bottom: 20px;
display: flex;
align-items: flex-start;
background: transparent !important;
d-form-label {
margin-right: 12px;
margin-top: 8px;
min-width: 60px;
flex-shrink: 0;
}
d-form-control {
flex: 1;
max-width: 200px;
}
}
}
}
// 覆盖 DevUI 默认样式
:host ::ng-deep {
form[dForm] {
background: transparent !important;
border: none !important;
}
}
用 !important 和 ::ng-deep 是为了覆盖 DevUI 的默认样式。如果不加,可能会看到表单区域有背景色或边框,和弹窗不协调。
五、常见问题
1. 按钮状态更新
保存按钮默认是禁用的,只有当表单验证通过时才启用。这需要在表单的 valueChanges 中调用 canConfirm:
typescript
this.userForm.valueChanges.subscribe(() => {
if (this.data?.canConfirm) {
this.data.canConfirm(this.userForm.valid);
}
});
2. 数据格式转换
表格数据和表单数据格式可能不一样。比如表格里性别是"男"/"女",表单里是"male"/"female"。需要在 loadUserData 和 onSubmit 中做转换。
3. 表单验证
表单提交时,如果验证不通过,需要标记所有字段为 touched,这样错误信息才会显示出来。
总结
用 DialogService 实现弹窗表单,比直接用 d-modal 组件要简单很多。不需要手动管理显示隐藏,不需要 ViewChild 和生命周期钩子,代码更清晰。唯一需要注意的是要提供所有依赖的服务,以及处理好数据格式转换。
如果遇到问题,先检查 app.config.ts 里的服务提供者是否齐全,然后看看数据格式转换是否正确。基本上这两点解决了,功能就能正常工作了。
参考资源
DevUI官网:https://devui.design/home