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

Angular中如何使用FormBuilder验证带动态值的表单数组

Angular 动态表单(FormArray)字段校验及报错修复方案

你遇到的Cannot read properties of null (reading 'invalid')报错主要有2个核心原因:

  1. 模板中调用data.get()时参数没有加引号,你写的data.get(subject)会被解析为读取组件内的subject变量,而不是获取名为subject的表单控件,找不到变量返回null,调用invalid就会抛出报错。
  2. 初始化PersonalData FormArray时,错误使用了formBuilder.control,而每个子项实际是FormGroup类型,类型不匹配也会导致控件读取异常。

第一步:修复TypeScript逻辑

调整表单初始化、动态行创建逻辑,给每个动态字段加上必填校验规则:

// 全局变量控制提交状态,和Name字段的校验逻辑对齐
submitted = false;

constructor(private formBuilder: FormBuilder) {}

ngOnInit() {
  this.SettingsForm = this.formBuilder.group({
    id: [''], 
    Name: ['', Validators.required],
    // 原control改为group,默认初始化第一行表单
    PersonalData: this.formBuilder.array([this.newPersonalData()], Validators.required)
  });
}

PersonalData() : FormArray {
  return this.SettingsForm.get("PersonalData") as FormArray
}

newPersonalData(): FormGroup {
  return this.formBuilder.group({
    id: '',
    // 每个字段添加必填校验
    subject: ['', Validators.required],
    mark: ['', Validators.required],
    // 多选框默认值为数组格式,添加必填校验
    exams: [[], Validators.required],
    active: [true] // 模板中用到了active字段,提前定义避免取值报错
  })
}

addPersonalData() {
  this.PersonalData().push(this.newPersonalData());
}

deletedata(index: number) {
  // 最少保留1行,只有1行时标记为隐藏,有多行直接移除
  if(this.PersonalData().length > 1) {
    this.PersonalData().removeAt(index);
  } else {
    this.PersonalData().at(index).get('active')?.setValue(false);
  }
}

// 提交方法
SettingsFormSubmit() {
  this.submitted = true;
  // 表单整体校验不通过则终止提交
  if(this.SettingsForm.invalid) {
    // 遍历所有动态行,手动触发所有控件校验状态更新,确保所有错误提示都正常展示
    this.PersonalData().controls.forEach(group => {
      group.markAllAsTouched();
    });
    return;
  }
  // 校验通过后执行你的提交逻辑
  console.log(this.SettingsForm.value);
}

第二步:修复HTML模板

给data.get()的参数加上引号,同时和提交状态submitted、控件touched状态联动,避免页面刚加载就展示错误提示:

<form [formGroup]="SettingsForm" (ngSubmit)="SettingsFormSubmit()">
  <div class="row">
    <div class="col-xl-4">
      <input type="text" placeholder="Name" formControlName="Name">
    </div>
  </div>
  <div class="row">
    <div class="col-xl-4 ">
      <p *ngIf="submitted && SettingsForm.get('Name')?.invalid" class="text-danger">
        Name is required.
      </p>
    </div>
  </div>
  <div class="row">
    <div class="col-xl-12">
      <table class="table" formArrayName="PersonalData">
        <thead>
          <tr>
            <th>Subject</th>
            <th>Mark</th>
            <th>Exams</th>
            <th>Delete</th>
            <th><button type="button" (click)="addPersonalData()" class="btn-blank">Add</button></th>
          </tr>
        </thead>
        <tbody>
          <tr [hidden]="!data.value.active" *ngFor="let data of PersonalData().controls;let i=index" [formGroupName]="i">
            <td>
              <select class="form-control" formControlName="subject">
                <option value="">Select Subject</option>
                <ng-container *ngFor="let subject of subjects">
                  <option [value]="subject.id">{{ subject.subject_name }}</option>
                </ng-container>
              </select>
              <!-- 字段名加引号,加安全操作符?.避免空指针 -->
              <ng-container *ngIf="submitted && data.get('subject')?.invalid && data.get('subject')?.touched">
                <span class="text-danger">请选择科目</span>
              </ng-container>
            </td>
            <td>
              <select class="form-control" formControlName="mark">   
                <option value="">Select Mark</option>
                <option>50</option>
                <option>60</option>
                <option>90</option>
              </select>
              <ng-container *ngIf="submitted && data.get('mark')?.invalid && data.get('mark')?.touched">
                <span class="text-danger">请选择分数</span>
              </ng-container>
            </td>
            <td>
              <mat-form-field>
                <mat-label>Select Exams</mat-label>
                <mat-select multiple formControlName="exams">
                  <mat-option *ngFor="let term of terms" [value]="term.id">
                    {{term.name}}
                  </mat-option>
                </mat-select>
              </mat-form-field>
              <ng-container *ngIf="submitted && data.get('exams')?.invalid && data.get('exams')?.touched">
                <span class="text-danger">请至少选择一项考试</span>
              </ng-container>
            </td>
            <td><button type="button" (click)="deletedata(i)" >Delete</button></td>
            <td><button type="button" (click)="addPersonalData()" >Add</button></td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
  <div class="modal-footer">
    <button type="submit" class="btn btn-success">Submit</button>
  </div>
</form>

优化说明

  • 所有select的默认选项都添加了value="",和表单控件的初始空值对应,避免默认选中异常
  • 新增了安全操作符?.,避免控件未初始化时读取属性抛出空指针报错
  • 提交时调用markAllAsTouched(),确保所有未填写的字段都会触发错误提示
  • 多选框exams初始值设为[]空数组,符合Angular多选组件的赋值要求,必填校验可以正常识别未选中的状态

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 16:06:03