You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 23:50:30