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>
已尝试方案
- 自定义验证器
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()) });
- 模板直接长度检查
<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
相关产品推荐
相关产品推荐

