如何在Angular 13中动态移除输入框的必填校验?
解决Angular 13中「至少填写一个字段」的动态校验问题
问题核心
你需要实现的是两个字段二选一必填:填了FirstName则NickName无需必填,填了NickName则FirstName无需必填,都为空时两个都要必填。原代码的问题出在:
- 用
addValidators/removeValidators动态修改校验的方式逻辑冗余,搭配valueChanges订阅容易触发循环 isMandatory的判断逻辑不符合需求:原逻辑是「所有字段为空时才加必填」,但实际需要的是「当一个字段有值时,另一个字段自动移除必填」
最优方案:用表单组自定义校验器
相比动态增删校验,给整个表单组加自定义校验器是更简洁可靠的方式,会自动触发校验更新,不用手动订阅valueChanges。
修改后的TS代码
import { Component, OnInit } from '@angular/core'; import { FormControl, FormGroup, Validators, ValidatorFn, AbstractControl } from '@angular/forms'; // 自定义校验器:确保至少一个字段有值 const atLeastOneRequired: ValidatorFn = (control: AbstractControl) => { const firstName = control.get('firstName')?.value; const nickName = control.get('nickName')?.value; // 两个都为空时返回错误 if (!firstName && !nickName) { return { atLeastOneRequired: true }; } return null; }; @Component({ selector: 'my-app', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent implements OnInit { public registerForm: FormGroup; ngOnInit() { this.registerForm = new FormGroup({ firstName: new FormControl(''), // 去掉初始必填校验 nickName: new FormControl(''), // 去掉初始必填校验 }, { validators: atLeastOneRequired }); // 给表单组加自定义校验 } }
修改后的HTML(添加错误提示)
<h1>Mandatory to add atleast one name</h1> <form [formGroup]="registerForm"> <div class="col-lg-3 col-md-3"> <mat-form-field> <mat-label>First Name</mat-label> <input matInput formControlName="firstName" /> <!-- 表单校验失败且当前字段为空时显示错误 --> <mat-error *ngIf="registerForm.hasError('atLeastOneRequired') && !registerForm.get('firstName').value"> 至少填写一个姓名 </mat-error> </mat-form-field> </div> <div class="col-lg-3 col-md-3"> <mat-form-field> <mat-label>Nick Name</mat-label> <input matInput formControlName="nickName" /> <mat-error *ngIf="registerForm.hasError('atLeastOneRequired') && !registerForm.get('nickName').value"> 至少填写一个姓名 </mat-error> </mat-form-field> </div> </form>
原代码栈溢出原因
调用updateValueAndValidity()不传参数时,默认emitEvent: true,会触发valueChanges订阅,而订阅逻辑里又会再次调用updateValueAndValidity(),形成无限循环导致栈溢出。就算传了{ emitEvent: false },原逻辑给每个字段单独订阅valueChanges,每次都遍历所有字段改校验,冗余逻辑也会导致不必要的触发。
可选优化:单独控制字段必填状态
如果你需要让单个字段的必填标记动态变化(比如填了FirstName后,NickName的必填提示消失),可以给字段加条件校验,搭配表单的valueChanges:
ngOnInit() { this.registerForm = new FormGroup({ firstName: new FormControl(''), nickName: new FormControl(''), }); // 订阅整个表单的值变化,动态设置字段校验 this.registerForm.valueChanges.subscribe(values => { const firstNameCtrl = this.registerForm.get('firstName'); const nickNameCtrl = this.registerForm.get('nickName'); // NickName有值则FirstName移除必填,否则加必填 values.nickName ? firstNameCtrl.removeValidators(Validators.required) : firstNameCtrl.addValidators(Validators.required); // FirstName有值则NickName移除必填,否则加必填 values.firstName ? nickNameCtrl.removeValidators(Validators.required) : nickNameCtrl.addValidators(Validators.required); // 更新校验时禁止触发valueChanges,避免循环 firstNameCtrl.updateValueAndValidity({ onlySelf: true, emitEvent: false }); nickNameCtrl.updateValueAndValidity({ onlySelf: true, emitEvent: false }); }); // 初始化时触发一次校验设置 this.registerForm.updateValueAndValidity({ emitEvent: false }); }
这种方式更贴近你最初的思路,但要注意:
- 只订阅整个表单的
valueChanges,别给每个字段单独订阅,避免重复触发 - 更新校验必须传
{ emitEvent: false },防止循环
内容的提问来源于stack exchange,提问作者Anirudh B
相关产品推荐
相关产品推荐

