Angular 响应式表单实践应用

如下图,是这次想要实现的功能。

  一个表格行,点击新增按钮就增加一行,点击后面的删除按钮就可以删除对应的行,其中有部分字段需要添加非空校验。要想实现这个功能,需要应用到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. 子组件初始化方式

在Angular中,子组件是先于父组件渲染的。所以,当子组件的初始化依赖于父组件异步加载的数据的时候,自组件的初始化方法就不能放在 ngOnInit 钩子中了,要放在 ngOnChanges 钩子中。父组件给子组件传值的时候,以及父组件改变传值数据的时候,会触发该函数。

ngOnInit(): void {}

ngOnChanges(): void {
    this.initData()
}

6. 回显表单组的方法

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]
      })
    }
  }

7. 自定义表单验证

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
  }
<nz-form-control [nzErrorTip]="frontTotalErrorTpl">
    <input nz-input formControlName="frontTotal" placeholder="请输入" />
    <ng-template #frontTotalErrorTpl let-control>
    <ng-container *ngIf="control.hasError('required')">请输入前期费用总额</ng-container>
    <ng-container *ngIf="!control.hasError('required') && control.hasError('isMoreThanZero')">请输入大于等于0的数字</ng-container>
    </ng-template>
</nz-form-control>

 8. 自定义表单验证触发时机

if(this.flag){
    this.validateForm.get('name').setValidators([Validators.required]); //设置name字段为必须验证
}else{
    this.validateForm.get('name').clearValidators()  //清除name字段验证
}

9.input框[disabled]属性失效时的写法

this.validateForm.controls.name.disable()
this.validateForm.controls.address.enable()

10.对于下拉框组件没通过校验但是表单不显示红字提示的情况 

可以使用markAllAsTouched方法来标记所有控件为已触摸状态以触发验证器的执行。

Object.values(this.validateForm.controls).forEach(control => {
  control.markAllAsTouched(); // 标记所有控件为已触摸状态
});

共通的触发验证方法

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()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mephisto180502

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值