如下图,是这次想要实现的功能。
一个表格行,点击新增按钮就增加一行,点击后面的删除按钮就可以删除对应的行,其中有部分字段需要添加非空校验。要想实现这个功能,需要应用到FormArray的知识。
步骤:
1. 声明一个FormGroup,里面包含FormArray,FormArray里面嵌套一组FormControl,由FormGroup管理
inputEnergyForm: FormGroup = new FormGroup({
inputEnergy: new FormArray([])
})
2. 添加一个属性,名字叫 inputEnergy,是一个get形式的属性。这样通过this.contacts就可以直接找到FormArray。
get inputEnergy () {
return this.inputEnergyForm.get('inputEnergy') as FormArray
}
3. 添加表单组的方法
addInputEnergy () {
// 新增下一行之前先验证当前行
if (this.inputEnergyForm.valid) {
// 创建表单组由FormGroup管理并初始化行数据
const inputEnergy: FormGroup = new FormGroup({
typeCode: new FormControl(null, Validators.required), // 必填
typeName: new FormControl(null), // 非必填,默认值为null
unit: new FormControl(null),
usage: new FormControl(null, Validators.required),
equivalentCoefficient: new FormControl(0, Validators.required),
coalUnit: new FormControl(null),
equivalentValue: new FormControl(0),
equalCoefficient: new FormControl(0),
equalValue: new FormControl(0)
})
// 把新增的表单组添加到FormArray
this.inputEnergy.push(inputEnergy)
// 正常不需要这行代码的,但是不知道为啥我的项目只有这么写才会触发数据的重新渲染
this.inputEnergy.controls = [...this.inputEnergy.controls]
} else {
// 未通过验证的红字提示未通过验证原因
for (const i in this.inputEnergy.controls) {
// 不同结构的FormGroup验证方法略有不同,写了一个共通的方法。
this.markFormDirty(<FormGroup>this.inputEnergy.controls[i])
}
}
}
4. 删除表单组的方法
delInputEnergy(i: number) {
// 通过下标移除
this.inputEnergy.removeAt(i)
// 正常不需要这行代码的,但是不知道为啥我的项目只有这么写才会触发数据的重新渲染
this.inputEnergy.controls = [...this.inputEnergy.controls]
}
5. 回显表单组的方法
initInputEnergyInfo() {
if (this.energyInfo.inputEnergy) {
this.energyInfo.inputEnergy.forEach((item: any, index: number) => {
const inputEnergy: FormGroup = new FormGroup({
typeCode: new FormControl(item.typeCode, Validators.required),
typeName: new FormControl(item.typeName),
unit: new FormControl(item.unit),
usage: new FormControl(item.usage, Validators.required),
equivalentCoefficient: new FormControl(item.equivalentCoefficient, Validators.required),
coalUnit: new FormControl(item.coalUnit),
equivalentValue: new FormControl(item.equivalentValue),
equalCoefficient: new FormControl(item.equalCoefficient),
equalValue: new FormControl(item.equalValue)
})
this.inputEnergy.insert(index, inputEnergy)
this.inputEnergy.controls = [...this.inputEnergy.controls]
})
}
}
6. 自定义表单验证
FormGroup = new FormGroup({
value: new FormControl(item.value, [Validators.required, this.isMoreThanZero])
})
// 在非空验证基础上额外添加数字必须大于0的表单验证
isMoreThanZero(control: FormControl) {
if (isNaN(Number(control.value)) || control.value <= 0) {
// 注意,这里返回的是isMoreThanZero,才能对应.hasError('isMoreThanZero')
return { isMoreThanZero: true };
}
return null
}
共通的触发验证方法
markFormDirty(form: FormGroup) {
this.markGroupDirty(form);
}
markControlDirty(formControl: FormControl, key: string) {
formControl.markAsDirty();
formControl.updateValueAndValidity();
if (formControl.invalid) {
console.log(`invalid key: ${key}`);
}
}
markGroupDirty(formGroup: FormGroup) {
Object.keys(formGroup.controls).forEach(key => {
switch (formGroup?.get(key)?.constructor?.name) {
case 'FormGroup':
this.markGroupDirty(formGroup.get(key) as FormGroup);
break;
case 'FormArray':
this.markArrayDirty(formGroup.get(key) as FormArray, key);
break;
case 'FormControl':
this.markControlDirty(formGroup.get(key) as FormControl, key);
break;
}
})
}
markArrayDirty(formArray: FormArray, key: string) {
formArray.controls.forEach(control => {
switch (control.constructor.name) {
case 'FormGroup':
this.markGroupDirty(control as FormGroup);
break;
case 'FormArray':
this.markArrayDirty(control as FormArray, key);
break;
case 'FormControl':
this.markControlDirty(control as FormControl, key);
break;
}
});
}
此次开发采用父子组件开发模式,涉及了父子组件通信。
① 父组件传递给子组件数据
在父组件的模板中插入子组件模板,并将父组件中的energyInfo传递给子组件。
<div>
<app-energy-form [energyInfo]="energyInfo"></app-energy-form>
</div>
子组件接收energyInfo
@Input() energyInfo: any
② 子组件传递给父组件数据并调用父组件的方法
在子组件处理好数据之后通过调用父组件的receiveEnergyInfo方法的方式将energyInfo作为参数回传给父组件。
子组件
@Output() reback = new EventEmitter<any>()
this.reback.emit(energyInfo)
父组件在引用子组件标签处添加 reback 属性
<div>
<app-energy-form (reback)="receiveEnergyInfo($event)"></app-energy-form>
</div>
并在ts中声明对应方法receiveEnergyInfo
receiveEnergyInfo(info: any) {
console.log('父组件收到子组件的回传数据',info)
}
③ 父组件调用子组件的方法
点击父组件中的提交按钮可以手动触发子组件的表单验证
在父组件引入子组件标签处可以给子组件标签起个标识 #energyForm
<app-energy-form #energyForm></app-energy-form>
这样就可以在父组件中通过 energyForm. 的方式,调用子组件的check方法了。
this.energyForm.check()