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

Angular响应式表单:验证嵌套FormArray非空并显示mat-error

Angular响应式表单嵌套FormArray非空验证问题

问题描述

使用Angular响应式表单,存在嵌套FormArray结构:外层groupFiles为FormArray,每个分组内包含files FormArray。需实现files数组为空时,通过mat-error显示错误提示,但当前尝试的两种方案均未生效。

现有代码

组件代码

import { FormBuilder, FormGroup, FormArray, Validators } from '@angular/forms';

export class MyComponent {
  serviceRequestForm: FormGroup;

  constructor(private fb: FormBuilder) {
    this.serviceRequestForm = this.fb.group({
      groupFiles: this.fb.array([])
    });
  }

  get groupFiles(): FormArray {
    return this.serviceRequestForm.get('groupFiles') as FormArray;
  }

  addGroupFile(groupFile: any) {
    const fileControls = this.fb.array([
      this.fb.group({
        fileName: ['', Validators.required],
        is_high: [false, Validators.required],
        uploadFileName: '',
        fileType: '',
        fileBase64: '',
      })
    ]);

    const group = this.fb.group({
      groupFileId: [groupFile.ID],
      groupName: [groupFile.DISPLAY_DESCRIPTION, Validators.required],
      orderNo: [groupFile.ORDER_NO, Validators.required],
      files: fileControls
    });

    this.groupFiles.push(group);
  }

  removeGroupFile(index: number) {
    this.groupFiles.removeAt(index);
  }
}

模板代码

<div formArrayName="groupFiles">
  <div *ngFor="let groupFile of groupFiles.controls; let i = index" [formGroupName]="i">
    <span class="col-sm-12">{{ i + 1 }}</span>
    <mat-form-field style="width: 80%" appearance="outline">
      <mat-label>Group Name</mat-label>
      <input matInput type="text" formControlName="groupName" id="groupName" />
      <mat-error *ngIf="groupFile.get('groupName')?.hasError('required')">
        Field is required
      </mat-error>
      <mat-error *ngIf="groupFile.get('files')?.value?.length === 0">
        Files are required for this group.
      </mat-error>
      <button mat-icon-button color="warn" type="button" matSuffix (click)="removeGroupFile(i)">
        <mat-icon>delete</mat-icon>
      </button>
    </mat-form-field>
  </div>
</div>

已尝试方案

  1. 自定义验证器
import { AbstractControl, ValidatorFn } from '@angular/forms';

export function filesNotEmptyValidator(): ValidatorFn {
  return (formArray: AbstractControl) => {
    const files = formArray.value;
    return files && files.length > 0 ? null : { filesNotEmpty: true };
  };
}

应用到files FormArray:

const group = this.fb.group({
  groupFileId: [groupFile.ID],
  groupName: [groupFile.DISPLAY_DESCRIPTION, Validators.required],
  orderNo: [groupFile.ORDER_NO, Validators.required],
  files: this.fb.array(fileControls, filesNotEmptyValidator())
});
  1. 模板直接长度检查
<mat-error *ngIf="groupFile.get('files')?.value?.length === 0">
  Files are required for this group.
</mat-error>

解决方案

1. 完善验证器触发逻辑

当files数组元素添加/移除时,手动更新验证状态,确保验证器重新执行:

// 移除单个文件方法
removeFile(groupIndex: number, fileIndex: number) {
  const group = this.groupFiles.at(groupIndex) as FormGroup;
  const files = group.get('files') as FormArray;
  files.removeAt(fileIndex);
  // 更新验证状态
  files.updateValueAndValidity({ emitEvent: true });
}

// 添加单个文件方法
addFile(groupIndex: number) {
  const group = this.groupFiles.at(groupIndex) as FormGroup;
  const files = group.get('files') as FormArray;
  files.push(this.fb.group({
    fileName: ['', Validators.required],
    is_high: [false, Validators.required],
    uploadFileName: '',
    fileType: '',
    fileBase64: '',
  }));
  files.updateValueAndValidity({ emitEvent: true });
}

2. 调整mat-error显示条件

mat-error默认仅在关联控件被标记为touched或表单提交时显示,需结合控件错误状态与交互状态:

<div formArrayName="groupFiles">
  <div *ngFor="let groupFile of groupFiles.controls; let i = index" [formGroupName]="i">
    <span class="col-sm-12">{{ i + 1 }}</span>
    <mat-form-field style="width: 80%" appearance="outline">
      <mat-label>Group Name</mat-label>
      <input matInput type="text" formControlName="groupName" id="groupName" />
      <mat-error *ngIf="groupFile.get('groupName')?.hasError('required') && (groupFile.get('groupName')?.touched || serviceRequestForm.submitted)">
        Field is required
      </mat-error>
      <mat-error *ngIf="groupFile.get('files')?.hasError('filesNotEmpty') && (groupFile.get('groupName')?.touched || serviceRequestForm.submitted)">
        Files are required for this group.
      </mat-error>
      <button mat-icon-button color="warn" type="button" matSuffix (click)="removeGroupFile(i)">
        <mat-icon>delete</mat-icon>
      </button>
    </mat-form-field>
    <!-- 文件操作按钮 -->
    <button mat-button (click)="addFile(i)">Add File</button>
    <div formArrayName="files">
      <div *ngFor="let file of (groupFile.get('files') as FormArray).controls; let j = index" [formGroupName]="j">
        <input matInput formControlName="fileName" placeholder="File Name">
        <button mat-icon-button (click)="removeFile(i,j)">
          <mat-icon>delete</mat-icon>
        </button>
      </div>
    </div>
  </div>
</div>

3. 可选:将验证器绑定到父FormGroup

若希望整个分组的有效性受files数组影响,可将验证器添加到父Group,确保表单提交时能检测错误:

const group = this.fb.group({
  groupFileId: [groupFile.ID],
  groupName: [groupFile.DISPLAY_DESCRIPTION, Validators.required],
  orderNo: [groupFile.ORDER_NO, Validators.required],
  files: fileControls
}, { validators: filesNotEmptyValidator() });

同时修改验证器适配FormGroup:

export function filesNotEmptyValidator(): ValidatorFn {
  return (formGroup: AbstractControl) => {
    const files = formGroup.get('files')?.value;
    return files && files.length > 0 ? null : { filesNotEmpty: true };
  };
}

模板中错误判断改为:

<mat-error *ngIf="groupFile.hasError('filesNotEmpty') && (groupFile.get('groupName')?.touched || serviceRequestForm.submitted)">
  Files are required for this group.
</mat-error>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 22:50:54