Angular问题:FormArray内FormGroup的ErrorStateMatcher无法生效
问题描述
- 单个起止日期表单功能正常,但使用FormArray实现多对起止日期的动态表单时,FormArray内每个FormGroup的验证规则以及ErrorStateMatcher均无法生效。
- 核心代码结构如下:
/* error state matchers */ readonly startDateAfterEndDateMatchers: SingleErrorStateMatcher[] = []; /* lifecycle hooks */ protected ngOnInit(): void { // initialisation this.initFormGroup(); this.formGetters = this.initFormGetters(this.datesInfo); // create error state matchers for (let i = 0; i < this.datesArray.length; i++) { this.startDateAfterEndDateMatchers.push( new SingleErrorStateMatcher('startDateAfterEndDate') ); } } // INITIALISE FORM private initFormGroup() { this.datesInfo = this.formBuilder.group({ datesArray: this.formBuilder.array( (this.datesArray || []).map((_) => this.formBuilder.group( { startDate: [ '', { nonNullable: true, validators: [Validators.required], }, ], endDate: [ '', { validators: [], }, ], }, { validators: [this.startDateAfterEndDateMatcher] } ) ) ), }); }
解决方案
1. 修正FormGroup验证器绑定问题
你传入的this.startDateAfterEndDateMatcher大概率不是符合规范的验证函数,或者未正确绑定上下文。验证器需要接收AbstractControl参数并返回验证结果,先定义正确的跨字段验证器:
// 定义跨字段验证器:检查开始日期是否晚于结束日期 startDateAfterEndDateValidator(control: FormGroup): ValidationErrors | null { const startDate = control.get('startDate')?.value; const endDate = control.get('endDate')?.value; if (startDate && endDate && new Date(startDate) > new Date(endDate)) { return { startDateAfterEndDate: true }; } return null; } // 在初始化表单时使用该验证器,注意绑定上下文 private initFormGroup() { this.datesInfo = this.formBuilder.group({ datesArray: this.formBuilder.array( (this.datesArray || []).map((_) => this.formBuilder.group( { startDate: [ '', { nonNullable: true, validators: [Validators.required], }, ], endDate: ['', { validators: [] }], }, { validators: [this.startDateAfterEndDateValidator.bind(this)] } ) ) ), }); }
2. 自定义ErrorStateMatcher适配FormGroup
Angular Material默认的ErrorStateMatcher只检测单个控件状态,要检测FormGroup的错误,需要自定义Matcher并关联对应组:
import { ErrorStateMatcher } from '@angular/material/core'; import { FormGroup, FormControl } from '@angular/forms'; export class GroupErrorStateMatcher implements ErrorStateMatcher { constructor(private errorKey: string, private formGroup: FormGroup) {} isErrorState(control: FormControl | null, form: FormGroup | null): boolean { // 同时检查FormGroup错误、控件触碰状态或表单提交状态 const hasGroupError = this.formGroup.hasError(this.errorKey); const isControlTouched = control?.touched || false; const isFormSubmitted = form?.submitted || false; return hasGroupError && (isControlTouched || isFormSubmitted); } }
在组件中创建Matcher时,直接关联FormArray内的每个FormGroup:
readonly startDateAfterEndDateMatchers: GroupErrorStateMatcher[] = []; protected ngOnInit(): void { this.initFormGroup(); this.formGetters = this.initFormGetters(this.datesInfo); const formArray = this.datesInfo.get('datesArray') as FormArray; // 为每个FormGroup创建对应的Matcher this.startDateAfterEndDateMatchers = formArray.controls.map(group => new GroupErrorStateMatcher('startDateAfterEndDate', group as FormGroup) ); }
3. 模板中正确绑定验证和Matcher
循环渲染FormArray时,将Matcher绑定到对应控件,并通过FormGroup判断错误:
<form [formGroup]="datesInfo"> <div formArrayName="datesArray"> <div *ngFor="let group of datesInfo.get('datesArray')['controls']; let i = index" [formGroupName]="i"> <mat-form-field> <input matInput formControlName="startDate" [errorStateMatcher]="startDateAfterEndDateMatchers[i]"> <mat-error *ngIf="group.hasError('startDateAfterEndDate')">开始日期不能晚于结束日期</mat-error> <mat-error *ngIf="group.get('startDate')?.hasError('required')">开始日期为必填项</mat-error> </mat-form-field> <mat-form-field> <input matInput formControlName="endDate" [errorStateMatcher]="startDateAfterEndDateMatchers[i]"> <mat-error *ngIf="group.hasError('startDateAfterEndDate')">结束日期不能早于开始日期</mat-error> </mat-form-field> </div> </div> </form>
4. 动态操作表单时同步更新Matcher
如果有添加/删除日期组的功能,操作后要同步更新Matcher数组:
addDateGroup() { const formArray = this.datesInfo.get('datesArray') as FormArray; const newGroup = this.formBuilder.group( { startDate: ['', { nonNullable: true, validators: [Validators.required] }], endDate: ['', { validators: [] }], }, { validators: [this.startDateAfterEndDateValidator.bind(this)] } ); formArray.push(newGroup); this.startDateAfterEndDateMatchers.push( new GroupErrorStateMatcher('startDateAfterEndDate', newGroup) ); } removeDateGroup(index: number) { const formArray = this.datesInfo.get('datesArray') as FormArray; formArray.removeAt(index); this.startDateAfterEndDateMatchers.splice(index, 1); }
内容的提问来源于stack exchange,提问作者Sachin
相关产品推荐
相关产品推荐

