Angular - 无法让 ErrorStateMatcher 与 FormArray 内的 FormGroup 一起使用

问题描述 投票:0回答:1

这适用于一个简单的表单,只有一个开始和结束日期,但现在我的表单是动态的,因此有多对开始和结束日期,所以我必须使用表单数组。

这是结构,但现在我无法让 errorMatchers 或每个 FormGroup(在 FormArray 内)上的验证工作

/* 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] }
          )
        )
      ),
    });
  }

这也是 stackblitz(它使用 Angular Material 2 和 3 组件) 任何帮助将不胜感激:https://stackblitz.com/edit/stackblitz-starters-ss9qeg?file=src%2Fmain.ts

提前致谢,

ST

angular
1个回答
0
投票

您的

ErrorStateMatcher
由于这种情况而无法工作的原因:

formGroup?.hasError(this.errorCode)

返回

false
。相信来自
formGroup
方法的
isErrorState
将获得带有
FormGroup
标签的根
<form>
,而不是当前表单控件的父表单组。

isErrorState(
  control: FormControl | null,
  formGroup: FormGroupDirective | NgForm | null
)

因此,您需要自定义

ErrorStateMatcher
以提供当前
FormGroup
的父级
FormControl

export class SingleErrorStateMatcher implements ErrorStateMatcher {
  private errorCode: string;
  public constructor(errorCode: string, private formGroup?: FormGroup) {
    this.errorCode = errorCode;
  }

  isErrorState(
    control: FormControl | null,
    formGroup: FormGroupDirective | NgForm | null
  ): boolean {
    let parentFormGroup = this.formGroup ?? formGroup;

    return (
      !!(parentFormGroup?.dirty || parentFormGroup?.touched) &&
      !!(parentFormGroup?.invalid && parentFormGroup?.hasError(this.errorCode))
    );
  }
}

以及当前将

SingleErrorStateMatcher
推入
startDateAfterEndDateMatchers
数组的方法。

for (let i = 0; i < this.datesArray.length; i++) {
  this.startDateAfterEndDateMatchers.push(
    new SingleErrorStateMatcher(
      'startDateAfterEndDate',
      this.datesInfo.controls['datesArray'].get(`${i}`) as FormGroup
    )
  );
}

此外,根据您的 HTML,您可以创建 3 个独立的

FormGroup
实例。

您的 HTML 结构应如下所示:

<form [formGroup]="datesInfo" class="form-group">
  <!-- dates array -->
  <div formArrayName="datesArray">
    @for (date of datesArray; track $index) {

      <ng-container [formGroupName]="$index">
        <!-- start date -->
        <mat-form-field class="form-date">
          <!-- label -->
          <mat-label> Start Date </mat-label>

          <!-- input -->
          <input
            matInput
            id="startDate-{{$index}}"
            [matDatepicker]="startDatePicker"
            [errorStateMatcher]="startDateAfterEndDateMatchers[$index]"
            [formControl]="formGetters[$index]!.startDate"
            autocomplete="off"
            readonly
            required
          />

          <!-- hint -->
          <mat-hint>DD/MM/YYYY</mat-hint>

          <!-- picker -->
          <mat-datepicker-toggle
            matIconSuffix
            [for]="startDatePicker"
            [disabled]="false"
          ></mat-datepicker-toggle>
          <mat-datepicker
            #startDatePicker
            [startAt]="(formGetters[$index]?.startDate?.value ?? null)"
          ></mat-datepicker>

          <!-- errors -->
          <mat-error
            *ngIf="formGetters[$index]?.startDate?.invalid
                  && (formGetters[$index]?.startDate?.dirty || formGetters[$index]?.startDate?.touched)"
          >
            @if(formGetters[$index]?.startDate?.errors?.['required']) { 
              Start Date is required. 
            }
          </mat-error>
          <mat-error
            *ngIf="datesInfo.get('datesArray')!.get([$index])?.hasError('startDateAfterEndDate')"
          >
            Cannot be after End Date
          </mat-error>
        </mat-form-field>

        <!-- end date -->
        <mat-form-field class="form-date">
          <!-- label -->
          <mat-label> End Date </mat-label>

          <!-- input -->
          <input
            (keydown)="endDatePicker.open()"
            (click)="endDatePicker.open()"
            matInput
            id="endDate-{{$index}}"
            [matDatepicker]="endDatePicker"
            [errorStateMatcher]="startDateAfterEndDateMatchers[$index]"
            [formControl]="formGetters[$index]!.endDate"
            autocomplete="off"
          />

          <!-- hint -->
          <mat-hint>DD/MM/YYYY</mat-hint>

          <!-- picker -->
          <mat-datepicker-toggle
            matIconSuffix
            [for]="endDatePicker"
            [disabled]="false"
          ></mat-datepicker-toggle>
          <mat-datepicker
            #endDatePicker
            [startAt]="(formGetters[$index]?.endDate?.value ?? null)"
          ></mat-datepicker>

          <!-- errors -->
          <mat-error
            *ngIf="formGetters[$index]?.endDate?.invalid
                  && (formGetters[$index]?.endDate?.dirty || formGetters[$index]?.endDate?.touched)"
          >
            @if(formGetters[$index]?.endDate?.errors?.['required']) { End Date is required. }
          </mat-error>
          <mat-error
            *ngIf="datesInfo.get('datesArray')!.get([$index])?.hasError('startDateAfterEndDate')"
          >
            Cannot be before Start Date
          </mat-error>
        </mat-form-field>

      </ng-container>
    }
  </div>
</form>

演示@StackBlitz

© www.soinside.com 2019 - 2024. All rights reserved.