使用 Angular 实现创建具有依赖项的动态表单






4.80/5 (7投票s)
如何使用 JSON 模板动态渲染相互依赖的表单控件
引言
在本文中,我们将提出一个解决方案,演示如何使用 JSON 模板动态渲染彼此的值相互依赖的表单控件。
我的解决方案基于拥有一个抽象的表单控件,以及一个中心表单容器,因为每个控件都会在需要时向父表单推送通知,以便通知其依赖项。
上面的类图显示
- 一个表单由实现
IFormControl
接口的控件组成 - 一个表单控件具有一个
ControlData
实例,用于渲染选项 - 每个控件都应该引用表单内部的一个方法,该方法将用于接收来自该控件的消息,并将该消息广播给其依赖项。
基本场景
- 用户编辑表单输入
- 输入控件检查它是否有依赖项
- 如果是
- 将包含参数(控件名称和依赖项)的消息发送到表单
- 表单处理调用并遍历所有控件,然后将消息转发给匹配依赖项名称的控件。
实现
我们将使用 Angular 框架和 typescript 语言来实现该解决方案,typescript 语言具有接口数据类型,这显然简化了实现并使其变得直接。
步骤 1
为数据创建接口。
export interface IControlData {
controlName: string;
controlType: string;
placeholder?: string;
dependents?: string[];
order?: number;
value?: any;
DependentKey?: any;
options?: Array<{
optionName: string;
value: string;
dependentKey?: any;
}>;
validators?: {
required?: boolean;
minlength?: number;
maxlength?: number;
};
}
在上面的代码中,我们创建了创建表单控件所需的所有属性。
示例
controlName
:用于保存每个FormControl
的名称Dependents
:依赖于此FormControl
的控件数组,必须根据业务逻辑在特定事件中通知它们
示例数据
export const FormData = [
{
controlName: 'Name',
controlType: 'text',
valueType: 'text',
dependents: ['Gender'],
placeholder: 'Enter name'
},
{
controlName: 'Type',
placeholder: 'Select Type',
controlType: 'radio',
dependents: ['Email'],
options: [{
optionName: 'Type A',
value: 'A'
}, {
optionName: 'Type B',
value: 'B'
}]
},
{
controlName: 'Gender',
placeholder: 'Select gender',
controlType: 'select',
dependents: ['Age', 'Books'],
options: [{
optionName: 'Male',
value: 'male'
}, {
optionName: 'Female',
value: 'female'
}],
validators: {
required: true
}
]
第二步
创建将由表单控件组件实现的接口。
export interface IControl {
controlData: IControlData;
sendMessage: EventEmitter<any>;
doSomething(params: any): void;
}
在上面的代码中,我们创建了一个类型为 IControlData
的属性 controlData
,用于保存每个控件的数据。
SendMessage
:事件发射器,用于调用父组件(表单组件)中的一个函数,该函数将通过表单将携带的消息广播给依赖控件。doSomething
:将用于详细说明FormControl
如何收到来自其他FormControl
更改的通知并在其上执行操作。
步骤 3
为每种类型的表单控件创建一个组件。
示例
typescript 文件
export class DropdownComponentComponent implements IControl, OnInit {
@Input() controlData: IControlData;
@Output() sendMessage: EventEmitter<any>;
constructor() {
this.sendMessage = new EventEmitter<any>();
}
ngOnInit() {
}
onchange(val: any): void {
this.controlData.value = val;
this.sendMessage.emit({value: 'dropdown ' + this.controlData.controlName,
controls: this.controlData.dependents});
}
doSomething(params: any) {
// filter dropdown by id ;
alert( this.controlData.controlName + ' received from ' + params);
}
}
在上面的代码中,为下拉列表 formControl
创建了一个组件,该组件实现了 IControl
接口。
它使用“@Input
”装饰器从父表单获取数据。
onChange
:当控件值更改时触发sendMessage
的函数,并将数据传递给父控件(动态表单),以便其依赖项收到此更改的通知。doSomething
:一个函数,仅发送一个警报,表明此控件收到了来自它所依赖的FormControl
的通知。
Html. 文件应该如下所示
<select [name]="controlData.controlName" [id]="controlData.controlName"
(change)="onchange($event.target.value)">
<option value="">{{controlData.placeholder}}</option>
<option *ngFor="let option of controlData.options"
[value]="option.value">{{option.optionName}}</option>
</select>
这是下拉列表的实现,它展示了我们将如何使用 Input 数据(Control Data)来渲染下拉列表控件。
*可以重复相同的事情来创建其他类型的控件。
步骤 4
创建动态表单组件,该组件将所有这些控件组合在一个表单组中。
export class DynamicFormComponent implements OnInit {
@Input() controlDataItems: IControlData[];
@ViewChildren('control') controls: QueryList<IControl>;
form: FormGroup;
submitted: boolean;
constructor() {
}
ngOnInit() {
const formGroup = {};
this.controlDataItems.forEach(formControl => {
formGroup[formControl.controlName] = new FormControl('');
});
this.form = new FormGroup(formGroup);
}
sendMessageToControls(params: any): void {
this.controls.forEach(control => {
if (params.controls.indexOf(control.controlData.controlName) > -1) {
control.doSomething(params.value);
}
});
}
在上面的代码中,ngOnInit
函数循环遍历 formData
,基于控件名称为每个控件创建一个 formControl
,然后将它们添加到 formGroup
中。
@ViewChildren
控件将允许从视图 DOM 中获取元素或指令的QueryList
。 每次添加、删除或移动子元素时,查询列表将被更新,并且查询列表的可观察更改将发出一个新值 [Angular 文档]。
这意味着每次子组件发生更改时,父组件都会收到通知。
sendMessageToControls
:一个由表单控件调用的函数,然后循环遍历控件以检查它是否依赖于任何其他子控件,从而调用“doSomething
”方法以获得此更改的通知并在其上执行操作。
动态表单的 HTML 文件应该如下所示
<div class="form-group" [formGroup]="form" >
<div *ngFor="let input of controlDataItems" class="form-row">
<label [attr.for]="input.controlName">{{input.controlName}}</label>
<div [ngSwitch]="input.controlType" >
<app-textbox-component *ngSwitchCase="'text'"
#control [controlData] ="input"
(sendMessage)="sendMessageToControls($event)" >
</app-textbox-component>
<app-dropdown-component #control *ngSwitchCase="'select'"
[controlData] ="input" (sendMessage)="sendMessageToControls($event)">
</app-dropdown-component>
<app-radio-component *ngSwitchCase="'radio'"
#control [controlData] ="input"
(sendMessage)="sendMessageToControls($event)">
</app-radio-component>
</div>
</div>
<button type="submit" [disabled]="form.invalid" (click)="submitForm()">
Submit
</button>
</div>
在此 HTML 页面中,*ngFor
用于循环遍历控件数据集合以分别获取每个控件数据,然后使用 *ngSwitch
基于控件类型创建输入控件。
结论
现在我们可以轻松创建具有依赖组件的动态表单,其中每个控件都将动态添加其属性,并且彼此之间可以更轻松地进行通信。
参考文献
历史
- 2020 年 4 月 17 日:初始版本