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

Angular响应式表单如何校验FormArray至少选中一个复选框

Angular动态表单「至少选中一个复选框」校验实现方案

1. 编写自定义聚合校验器

Angular内置校验器不支持FormArray子项的跨字段聚合校验,先新建专用校验函数,遍历检查FormArray中是否存在isAnswer为true的项:

import { AbstractControl, ValidatorFn, FormArray } from '@angular/forms';

/**
 * FormArray校验规则:至少有一个子项的isAnswer字段为true
 */
export function atLeastOneAnswerChecked(): ValidatorFn {
  return (control: AbstractControl): {[key: string]: boolean} | null => {
    const answerArray = control as FormArray;
    // 遍历检查是否存在选中项
    const hasChecked = answerArray.controls.some(item => {
      return item.get('isAnswer')?.value === true;
    });
    return hasChecked ? null : { required: true };
  };
}

2. 给answers FormArray绑定校验规则

修改表单初始化代码,创建answers FormArray时把自定义校验器作为第二个参数传入:

ngOnInit() {
  this.questionForm = this._formBuilder.group({
    label: ['', Validators.required],
    // 绑定自定义校验器到FormArray
    answers: this._formBuilder.array([], atLeastOneAnswerChecked()),
  });
  this.addOption();
}

注意:后续新增/删除表单子项、切换复选框状态时,FormArray会自动重新执行该校验规则,无需额外手动触发。

3. 修正模板错误提示逻辑

原有模板的错误提示缺少提交状态判断,和其他字段的校验显示逻辑保持一致即可,修改对应代码块:

<div
  class="invalid-feedback"
  *ngIf="
    formSubmitAttempt &&
    questionForm.get('answers')?.errors &&
    questionForm.get('answers')?.errors?.required
  "
>
  至少需要选中一个复选框作为答案
</div>

如果需要切换复选框时实时显示错误(无需等提交),可以给复选框控件加变更事件触发校验更新:

<input
  class="form-check-input"
  type="checkbox"
  formControlName="isAnswer"
  (change)="questionForm.get('answers')?.updateValueAndValidity()"
/>

4. 补充提交时的统一校验逻辑

在提交方法中统一标记表单提交状态、触发全量校验,避免漏判:

onSubmit(event: Event) {
  event.preventDefault();
  this.formSubmitAttempt = true;
  // 标记所有控件为已触碰状态,触发错误提示显示
  this.questionForm.markAllAsTouched();

  if (this.questionForm.invalid) {
    return;
  }

  // 校验通过后的业务提交逻辑
  console.log('表单合法,提交数据:', this.questionForm.value);
}

内容的提问来源于stack exchange,提问作者Rasik

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 04:42:10