什么是动态表单
“表单”这个词大家其实并不陌生,即angular中的form。所以对于“动态表单”的理解主要着眼于“动态”。就我个人理解而言,“动态”即是可以随情况的变动而进行变动的。按这个思想进行延伸并与“表单”结合起来,我大致将“动态表单”理解为:当用户进行不同的操作时,表单中的内容(如字段等)会发生动态变更。如下图:
角色选择学生时和选择老师时的所渲染的表单是不同的,且不仅仅是V层不同,C层中对应的formGroup中的字段也必须是不同的。再高级一点那就是赋予用户新增和删除不同角色的表单的字段的权利。如下图:
点击“addField”按钮后,其他角色就会(在V层和C层的formGroup中)新增“年龄”字段;点击“delete”后“年龄”字段便被删除。
动态表单demo的angular实现
注意:此dome完全在angular框架下写的,所以对于新增的字段的储存是以缓存的形式储存在Window.sessionStorage中,正式的开发此部分的内容应由后端进行处理。
实现思路
要完成表单到动态表单的变迁,首先我们应该了解,就angular的form表单而言,是由两个重要部分组成的:一是在V层与用户交互的UI,二是在C层中的用于接收字段的值的formGroup。在一般的表单中,二者都是已经写死的,即静态的。所以问题的关键便是上述二者的内容如何随着用户的操作而变更。简单思考一下:前者的动态实现需要将V层中静态的值替换成动态变量,后者的的动态实现即formGroup的动态创建也需要一个动态变量来作为参数(详情请看下文createFormGroup()方法,位于代码实现->C层)。我们能够发现:它们都需要一个动态变量。而这个动态变量需要能够由用户的操作而控制。我们设那个动态变量为A(位于C层中),其实我们还需要一个变量B(位于缓存中),用于处理字段的新增和删除。最终我的思路大致如下图:
代码实现
首先在angular项目中随便初始化一个组件(C层、V层、css文件、测试文件)
V层
<!--动态表单demo-->
<div class="row">
<div class="col-3">
<h2>动态表单</h2>
<div class="example-outlet">
<label>角色:</label>
<select [formControl]="formSelect" (change)="formSelectChange()">
<option value="student">学生</option>
<option value="teacher">老师</option>
<option value="other">其他</option>
</select>
<form [formGroup]="formGroup" (ngSubmit)="onSubmit(formGroup)">
<div *ngFor="let item of baseFrom">
<label>{{ item.label }}:</label>
<input type="text" formControlName="{{ item.key }}" placeholder="{{item.placeholder}}">
<button *ngIf="item.key !== 'name' && item.key !== 'number'" (click)="deleteField(item)">delete</button>
</div>
<button>submit</button>
</form>
</div>
</div>
<div class="col-3">
<h2>动态表单添加字段</h2>
<div class="example-outlet">
<form [formGroup]="formGroupForAddField" (ngSubmit)="addField(formGroupForAddField)">
<label>角色:</label>
<select formControlName="role">
<option value="student">学生</option>
<option value="teacher">老师</option>
<option value="other">其他</option>
</select>
<div>
<label>字段:</label>
<input type="text" placeholder="请输入字段名称" formControlName="field">
</div>
<button>add field</button>
</form>
</div>
</div>
</div>
C层
// 动态表单demo
baseFrom: any = [];
formGroup = this.createFormGroup(this.baseFrom);
formSelect = new FormControl(null);
formGroupForAddField = new FormGroup({
field: new FormControl('', [Validators.required]),
role: new FormControl('', [Validators.required]),
});
studentField: any = [];
teacherField: any = [];
otherField: any = [];
ngOnInit(): void {
this.initBseForm();
// @ts-ignore
const sessionStorage = JSON.parse(window.sessionStorage.getItem(this.formSelect.value));
if (sessionStorage !== null) {
for (const item of sessionStorage) {
const pre = [
{
key: item,
label: item,
value: null,
placeholder: '请输入' + item,
validators: [Validators.required]
}
];
this.baseFrom = [...this.baseFrom, ...pre];
}
}
console.log('ngOnIit', this.baseFrom);
this.formGroup = this.createFormGroup(this.baseFrom);
}
addField(formGroupForAddField: FormGroup): void {
if (formGroupForAddField.get('field')?.value !== '' && formGroupForAddField.get('role')?.value !== '') {
console.log('formGroupForAddField', formGroupForAddField.value);
const role = formGroupForAddField.get('role')?.value;
const field = formGroupForAddField.get('field')?.value;
let addField: any = [];
if (role === 'student') {
this.studentField[this.studentField.length] = field;
addField = this.studentField;
} else if (role === 'teacher') {
this.teacherField[this.teacherField.length] = field;
addField = this.teacherField;
} else if (role === 'other') {
this.otherField[this.otherField.length] = field;
addField = this.otherField;
}
window.sessionStorage.setItem(role, JSON.stringify(addField));
this.ngOnInit();
}
}
createFormGroup(options: any[]): FormGroup {
const group: any = {};
options.forEach(item => {
group[item.key] = new FormControl(item.value, item.validators);
});
return new FormGroup(group);
}
deleteField(item: any): void {
// @ts-ignore
const sessionStorage = JSON.parse(window.sessionStorage.getItem(this.formSelect.value));
const role = this.formGroupForAddField.get('role')?.value;
if (sessionStorage !== null) {
console.log('deleteField', sessionStorage);
this.deleteItemOfArr(sessionStorage, item);
this.deleteItemOfArr(this.baseFrom, item);
if (role === 'student') { this.deleteItemOfArr(this.studentField, item);
} else if (role === 'teacher') { this.deleteItemOfArr(this.teacherField, item);
} else if (role === 'other') { this.deleteItemOfArr(this.otherField, item); }
window.sessionStorage.setItem(this.formSelect.value, JSON.stringify(sessionStorage));
}
this.ngOnInit();
}
deleteItemOfArr(ary: [], el: string): [] {
// @ts-ignore
const index = ary.indexOf(el);
const delEle = ary.splice(index, 1);
return ary;
}
formSelectChange(): void {
this.ngOnInit();
}
initBseForm(): void {
if (this.formSelect.value === null || this.formGroupForAddField === null) {
this.formSelect.setValue('student');
this.formGroupForAddField.get('role')?.setValue('student');
}
this.baseFrom = [
{
key: 'name',
label: '姓名',
value: null,
placeholder: '请输入姓名',
validators: [Validators.required]
},
{
key: 'number',
label: '手机号',
value: null,
placeholder: '请输入手机号',
validators: [Validators.required]
}
];
}
onSubmit(formGroup: FormGroup): void {
console.log('formGroup', formGroup.value);
}
css文件
.example-outlet {
margin-bottom: 10px;
padding: 10px;
border: 1px dashed black;
width: 350px;
height: 250px;
}
效果预览
重构代码
最后重新审视这个动态表单demo,发现一股脑的将代码写在一个组件里并不是一个好习惯。每个人所处的团队规范或正在开发的项目规范不同,所以就代码重构这一块儿而言还请大家根据自己的实际情况进行。对于我来说的话,我的重构思路是:在service层新建一个formService文件,将此demo的大部分方法修改成可通用的方法挪到formService文件中。这样做的好处一是可以使得此组件中代码显得更加简洁精炼,二是也可以供其他组件一同使用。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。