为什么 Angular 表单有效且没有 mat-error 而它有 formGroup.errors?

Why is an Angular form VALID with no mat-error when it has formGroup.errors?

我在 Angular 11 中使用反应式表单和自定义验证器。当用户选择早于 [= 的 EndDate 时,我想在表单级别标记错误16=]。我将使用此表单级错误在模板上显示错误文本并禁用提交表单的按钮。

这里好像有两个问题我想不通:

1.当我通过表单上的 customValidator 设置错误时,即使有 form.errorsform.status 仍然是 VALID。这是为什么?

2。在模板中,检查表单的错误不起作用。我有一个 mat-errorform.hasErrors('endDateTooSoon'),但此错误从未显示。我在这里错过了什么?有点奇怪。

这是表单实例化。

  shiftEditorForm = new FormGroup({
    shiftTitle: new FormControl('', Validators.required),
    shiftStartDate: new FormControl('', Validators.required),
    shiftStartTime: new FormControl('', Validators.required),
    shiftEndDate: new FormControl('', Validators.required),
    shiftEndTime: new FormControl('', Validators.required),
  }, DateValidators.compareDates('shiftStartDate', 'shiftEndDate', { endDateTooSoon: true})
 );

这是验证器:

  static compareDates = (
    dateField1: string,
    dateField2: string,
    validatorField: { [key: string]: boolean }): ValidatorFn => {
      return (control: AbstractControl): { [key: string]: boolean } | null => {
      const date1 = control.get(dateField1)?.value;
      const date2 = control.get(dateField2)?.value;
      if (date1 && date2 && date1 > date2) {
        return validatorField;
      }
      return null;
    };
  }

这是验证器工作时表单的样子:

(为什么正确添加错误后还是VALID?)

这是定义 shiftStartDate formControl 的模板块:

<mat-form-field class="date-selection"
    appearance="outline">
  <input matInput
    [matDatepicker]="startDate"
    formControlName="shiftStartDate">
  <mat-datepicker-toggle matSuffix [for]="startDate"></mat-datepicker-toggle>
  <mat-datepicker #startDate></mat-datepicker>
  <mat-error *ngIf="shiftStartDate?.touched && !shiftStartDate?.value">
    Shift start date is required.
  </mat-error>
  <mat-error *ngIf="shiftEditorForm?.hasError('endDateTooSoon')">
    End date must be after start date.
  </mat-error>
</mat-form-field>

我正在检查 *ngIf="shiftEditorForm.hasError(),但它从未显示错误。

shiftEndDate formControl 的模板还有另一个类似的块。

然而,当我操作日期选择器并将 shiftEndDate 设置在 shiftStartDate 之前时,没有任何反应,也没有显示错误:

更新: 我在发布这个问题后发现了一个额外的问题。提交表单的按钮 disabled/enabled 基于日期检查的有效性,在 button 上具有以下属性:[disabled]="this.shiftEditorForm.invalid"。真正令人头疼的是:formGroup 显示状态为 VALID 但对于此 invalid 布尔检查返回 true。

问题

我想我已经能够在此 stackblitz Demo

中重现您的问题

我注意到两件事,

  1. 您在验证时没有考虑时间
  2. 如您的问题所述,未显示验证错误...

我们将尝试查看第二个错误

问题分析

为了理解问题,我会声明您的代码完全符合预期...

下面应该都解释清楚了

<mat-error></mat-error>标签下添加以下代码

<span *ngIf="shiftEditorForm?.hasError('endDateTooSoon')">
    End date must be after start date.
</span>

您会注意到,当抛出日期错误时,显示的内容没有任何问题

简单地说,要显示 mat-error,那么与 <mat-error> 关联的 input 一定有错误。从表单中我们注意到,一旦用户输入一个值,shiftStartDate 就会生效,因此 <mat-error> 将不会显示,完全符合预期

解决您的问题

如果您需要显示 mat-error,那么您需要将控件设置为无效,例如

 control.get(dateField1)?.setErrors({higherThanStart: true}) 

你的 validatorFn 会是这样的

class DateValidators {
    static compareDates = (
    dateField1: string,
    dateField2: string,
    validatorField: { [key: string]: boolean }): ValidatorFn => {
      return (control: AbstractControl): { [key: string]: boolean } | null => {
      const date1 = control.get(dateField1)?.value;
      const date2 = control.get(dateField2)?.value;
      if (date1 && date2 && date1 > date2) {
         control.get(dateField1)?.setErrors({higherThanStart: true}) 
        return validatorField;
      }
      
      control.get(dateField1)?.setErrors({higherThanStart: null})
      return null;
    };
  }
}

现在错误将显示... See Demo Here

原来这里有两个问题

  1. mat-error 元素将 仅在 字段上存在 输入级错误 时显示-- 它必须是单个 formControl 上的错误。因此,当您在 mat-input 上执行 *ngIf 时,它必须是除了存在的隐式 ngIf 之外,以检查该表单字段上是否存在错误。这就是 *ngIf="shiftEditorForm?.hasError('')" 不起作用的原因。在输入级别的 formControl 上没有设置错误,因此即使通过验证器设置的 formGroup 上有错误,mat-error 也不会显示。

  2. 在 formGroup 级别附加验证器将 可以 检查 formControl 中的多个字段,这在这里是必需的,但是验证器的 return 值(error 对象或 null)将仅适用于 formGroup 而不适用于任何单独的 formControl 输入。因此需要在单个日期 formControls 上手动使用 setErrors({ [error]: true }) 以及 return 表单本身的错误。

验证器代码如下:

import { AbstractControl, ValidatorFn } from "@angular/forms";

export class DateValidators {

  static checkDateRange (
    dateField1: string,
    dateField2: string,
    error: string
  ): ValidatorFn {
      return (control: AbstractControl): { [key: string]: boolean } | null => {
      const date1 = control.get(dateField1);
      const date2 = control.get(dateField2)
      if (date1?.value && date2?.value && date1?.value > date2?.value) {
        date1.setErrors({ [error]: true })
        date2.setErrors({ [error]: true })
        return { [error]: true };
      }
      date1?.setErrors(null);
      date2?.setErrors(null);
      return null;
    };
  }
}

下面是将其附加到 formGroup 的方法:

DateValidators.checkDateRange('startDate', 'endDate', 'endDateTooSoon');

前两个参数是 formControl 输入的名称。第三个是模板中要附加检查的错误名称。

在弄清楚这一点后,我想我会 post 一个简洁的答案。欧文同时也弄明白了,我把他的回答标记为正确,因为他把这一切都很好地阐述了。我只是想添加一个摘要。