【问题标题】:Display custom validator error with mat-error使用 mat-error 显示自定义验证器错误
【发布时间】:2018-06-01 18:02:28
【问题描述】:

我来找你谈论有角材料的问题。其实我觉得是个问题,但我更喜欢先找个误会。

关于我的问题的第一件事是上下文,我尝试做一个包含两个输入的简单表单:密码及其确认。

user-form.component.ts

this.newUserForm = this.fb.group({
  type: ['', Validators.required],
  firstname: ['', Validators.required],
  lastname: ['', Validators.required],
  login: ['', Validators.required],
  matchingPasswordsForm: this.fb.group(
    {
      password1: ['', Validators.required],
      password2: ['', Validators.required],
    },
    {
      validator: MatchingPasswordValidator.validate,
    },
  ),
  mail: ['', [Validators.required, Validators.pattern(EMAIL_PATTERN)]],
  cbaNumber: [
    '411000000',
    [Validators.required, Validators.pattern(CBANUMBER_PATTERN)],
  ],
  phone: ['', [Validators.required, Validators.pattern(PHONE_PATTERN)]],
}

我的兴趣是匹配PasswordsForm FormGroup。你可以在上面看到验证器。

这里是验证器:

ma​​tching-password.validator.ts

export class MatchingPasswordValidator {
    constructor() {}

    static validate(c: FormGroup): ValidationErrors | null {
        if (c.get('password2').value !== c.get('password1').value) {
            return { matchingPassword: true};
        }
        return null;
    }
}

和 HTML。

user-form.component.html

<div class="row" formGroupName="matchingPasswordsForm">
    <mat-form-field class="col-md-6 col-sm-12">
        <input matInput placeholder="Mot de passe:" formControlName="password1">
        <mat-error ngxErrors="matchingPasswordsForm.password1">
            <p ngxError="required" [when]="['dirty', 'touched']">{{requiredMessage}}</p>
        </mat-error>
    </mat-form-field>

    <mat-form-field class="col-md-6 col-sm-12">
        <input matInput placeholder="Confirmez" formControlName="password2">
        <mat-error ngxErrors="matchingPasswordsForm.password2">
            <p ngxError="required" [when]="['dirty', 'touched']">{{requiredMessage}}</p>
        </mat-error>
        <!--                 -->
        <!-- problem is here -->
        <!--                 -->
        <mat-error ngxErrors="matchingPasswordsForm" class="mat-error">
            <p ngxError="matchingPassword" [when]="['dirty', 'touched']">{{passwordMatchErrorMessage}}</p>
        </mat-error>
        <!-- ^^^^^^^^^^^^^^^^ -->
        <!-- /problem is here -->
        <!--                  -->
    </mat-form-field>
</div>

我已经用 cmets 包围了有趣的代码。

现在,一些解释:使用标签,当触摸密码2时,会显示我的错误:

Password2 just touched

但是,当我输入错误的密码时,不再显示错误:

Wrong password2

首先我以为我误解了自定义验证器的使用。但是当我用整个东西替换时,效果很好!

用提示替换错误

<mat-hint ngxErrors="matchinghPasswordsForm">
    <p ngxError="matchingPassword" [when]="['dirty', 'touched']">{{passwordMatchErrorMessage}}</p>
</mat-hint>

With mat-hint tag

我希望我是清楚的,我真的很想在材料设计 github 上发布问题之前得到你的观点。

如果我误解了什么,请点燃我错过的东西。

最后一件事,我的测试是使用 ngxerrors 和 *ngif 完成的。为了更具可读性,我的代码示例仅使用 ngxerrors 。

提前感谢您抽出宝贵的时间。

【问题讨论】:

  • @AJT_82 :就像在回答您的链接时所说的那样,如果您更改了前日期,则不再显示错误消息,问题似乎和我的一样
  • 是的,因此制作一个错误状态匹配器;)

标签: angular angular-material2 custom-validators


【解决方案1】:

Alex 是正确的。您必须使用 ErrorStateMatcher。我必须做很多研究才能弄清楚这一点,而且没有一个单一的来源可以给我完整的答案。我不得不拼凑从多个来源获得的信息,以制定自己的问题解决方案。希望下面的例子能让你摆脱我所经历的头痛。

形式

这是一个使用 Angular Material 元素作为用户注册页面的表单示例。

<form [formGroup]="userRegistrationForm" novalidate>

    <mat-form-field>
        <input matInput placeholder="Full name" type="text" formControlName="fullName">
        <mat-error>
            {{errors.fullName}}
        </mat-error>
    </mat-form-field>

    <div formGroupName="emailGroup">
        <mat-form-field>
            <input matInput placeholder="Email address" type="email" formControlName="email">
            <mat-error>
                {{errors.email}}
            </mat-error>
        </mat-form-field>

        <mat-form-field>    
            <input matInput placeholder="Confirm email address" type="email" formControlName="confirmEmail" [errorStateMatcher]="confirmValidParentMatcher">
            <mat-error>
                {{errors.confirmEmail}}
            </mat-error>
        </mat-form-field>
    </div>

    <div formGroupName="passwordGroup">
        <mat-form-field>
            <input matInput placeholder="Password" type="password" formControlName="password">
            <mat-error>
                {{errors.password}}
            </mat-error>
        </mat-form-field>
    
        <mat-form-field>
            <input matInput placeholder="Confirm password" type="password" formControlName="confirmPassword" [errorStateMatcher]="confirmValidParentMatcher">
            <mat-error>
                {{errors.confirmPassword}}
            </mat-error>
        </mat-form-field>
    </div>

    <button mat-raised-button [disabled]="userRegistrationForm.invalid" (click)="register()">Register</button>

</form>

如您所见,我正在使用来自 Angular Material 的 &lt;mat-form-field&gt;&lt;input matInput&gt;&lt;mat-error&gt; 标签。我的第一个想法是添加*ngIf 指令来控制&lt;mat-error&gt; 部分何时出现,但这没有效果!可见性实际上是由&lt;mat-form-field&gt; 的有效性(和“触及”状态)控制的,并且没有提供验证器来测试与 HTML 或 Angular 中另一个表单字段的相等性。这就是确认字段上的errorStateMatcher 指令发挥作用的地方。

errorStateMatcher 指令内置在 Angular Material 中,并提供了使用自定义方法来确定 &lt;mat-form-field&gt; 表单控件的有效性的能力,并允许访问父级的有效性状态来执行此操作。要开始了解我们如何在这个用例中使用 errorStateMatcher,让我们先看一下组件类。

组件类

这是一个 Angular 组件类,它使用 FormBuilder 为表单设置验证。

export class App {
    userRegistrationForm: FormGroup;

    confirmValidParentMatcher = new ConfirmValidParentMatcher();

    errors = errorMessages;

    constructor(
        private formBuilder: FormBuilder
    ) {
        this.createForm();
    }

    createForm() {
        this.userRegistrationForm = this.formBuilder.group({
            fullName: ['', [
                Validators.required,
                Validators.minLength(1),
                Validators.maxLength(128)
            ]],
            emailGroup: this.formBuilder.group({
                email: ['', [
                    Validators.required,
                    Validators.email
                ]],
                confirmEmail: ['', Validators.required]
            }, { validator: CustomValidators.childrenEqual}),
            passwordGroup: this.formBuilder.group({
                password: ['', [
                    Validators.required,
                    Validators.pattern(regExps.password)
                ]],
                confirmPassword: ['', Validators.required]
            }, { validator: CustomValidators.childrenEqual})
        });
    }

    register(): void {
        // API call to register your user
    }
}

该类为用户注册表单设置一个FormBuilder。注意类中有两个FormGroups,一个用于确认电子邮件地址,一个用于确认密码。各个字段使用适当的验证器函数,但都在组级别使用自定义验证器,该验证器检查以确保每个组中的字段彼此相等,如果不相等则返回验证错误。

组的自定义验证器和 errorStateMatcher 指令的组合为我们提供了正确显示确认字段的验证错误所需的完整功能。让我们看一下自定义验证模块,将它们组合在一起。

自定义验证模块

我选择将自定义验证功能分解到自己的模块中,以便可以轻松地重用它。出于同样的原因,我还选择将与表单验证相关的其他内容放在该模块中,即正则表达式和错误消息。稍微提前考虑一下,您很可能也会允许用户在用户更新表单中更改他们的电子邮件地址和密码,对吧?这是整个模块的代码。

import { FormGroup, FormControl, FormGroupDirective, NgForm, ValidatorFn } from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material';

/**
 * Custom validator functions for reactive form validation
 */
export class CustomValidators {
    /**
     * Validates that child controls in the form group are equal
     */
    static childrenEqual: ValidatorFn = (formGroup: FormGroup) => {
        const [firstControlName, ...otherControlNames] = Object.keys(formGroup.controls || {});
        const isValid = otherControlNames.every(controlName => formGroup.get(controlName).value === formGroup.get(firstControlName).value);
        return isValid ? null : { childrenNotEqual: true };
    }
}

/**
 * Custom ErrorStateMatcher which returns true (error exists) when the parent form group is invalid and the control has been touched
 */
export class ConfirmValidParentMatcher implements ErrorStateMatcher {
    isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
        return control.parent.invalid && control.touched;
    }
}

/**
 * Collection of reusable RegExps
 */
export const regExps: { [key: string]: RegExp } = {
    password: /^(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]{7,15}$/
};

/**
 * Collection of reusable error messages
 */
export const errorMessages: { [key: string]: string } = {
    fullName: 'Full name must be between 1 and 128 characters',
    email: 'Email must be a valid email address (username@domain)',
    confirmEmail: 'Email addresses must match',
    password: 'Password must be between 7 and 15 characters, and contain at least one number and special character',
    confirmPassword: 'Passwords must match'
};

首先让我们看一下组的自定义验证器函数CustomValidators.childrenEqual()。由于我来自面向对象的编程背景,因此我选择将此函数设为静态类方法,但您也可以轻松地将其设为独立函数。该函数必须是ValidatorFn 类型(或适当的文字签名),并采用AbstractControl 类型或任何派生类型的单个参数。我选择将其设为FormGroup,因为这是它的用例。

函数的代码遍历FormGroup 中的所有控件,并确保它们的值都等于第一个控件的值。如果是,则返回null(表示没有错误),否则返回childrenNotEqual 错误。

因此,当字段不相等时,我们在组上的状态无效,但我们仍然需要使用该状态来控制何时显示错误消息。我们的 ErrorStateMatcher,ConfirmValidParentMatcher,可以为我们做到这一点。 errorStateMatcher 指令要求您指向一个类的实例,该实例在 Angular Material 中实现了提供的 ErrorStateMatcher 类。这就是这里使用的签名。 ErrorStateMatcher 需要实现isErrorState 方法,其签名显示在代码中。它返回truefalsetrue 表示存在错误,导致输入元素的状态无效。

这个方法的单行代码非常简单;如果父控件(我们的 FormGroup)无效,则返回 true(存在错误),但前提是已触摸该字段。这与 &lt;mat-error&gt; 的默认行为一致,我们将其用于表单上的其余字段。

为了将这一切结合在一起,我们现在有一个带有自定义验证器的 FormGroup,它在我们的字段不相等时返回错误,以及一个 &lt;mat-error&gt;,它在组无效时显示。要查看此功能的实际运行情况,请查看plunker,其中包含上述代码的实现。

另外,我已经将这个解决方案写到博客 here

【讨论】:

  • Mogsdad,感谢您提供的信息。这是我在 StackOverflow 上的第一个答案,所以我还在学习中。我已经查看了链接信息并相应地更新了我的帖子。请让我知道这是否可以接受。
  • 太棒了!感谢您按提示行事。祝你好运!
  • 我使用的是自定义 MatFormFieldControl,而不是 matInput。我能做什么?
  • 我不知道为什么它需要这么复杂...为什么 Reactive Forms 的团队不只是实现选项以在通过 FormBuilder 或 @ 创建 Form 时提供错误消息作为参数987654356@... 然后,创建某种全局提供程序,为Validation 对象配置默认消息。
  • reg 没有一个单一的来源可以给我完整的答案你正在处理谷歌的产品(Angular),预计会有相当多的学习曲线和频繁破坏版本升级。 注意:AFAIK,建议调用 this.createForm() 的方式是来自 ngOnInit,而不是 constructor
【解决方案2】:

如何创建自定义验证:

如果组件的内部属性'isValid'为false,则设置输入状态为错误,并显示信息。

HTML:

<input matInput
[formControl]="inputControl"
[placeholder]="placeholder"
[readonly]="readonly"
[errorStateMatcher]="matcher">

<mat-error *ngIf="!isValid">
Input not valid.
</mat-error>

TS:

isValid = true;

changeValitationStatus() {
this.matcher = new InputErrorStateMatcher(!this.isValid);
}

matcher = new InputErrorStateMatcher(!this.isValid);



class InputErrorStateMatcher implements ErrorStateMatcher {
    constructor(private errorstate: boolean) {}
    isErrorState(control: FormControl|null, form: FormGroupDirective|NgForm|null):boolean {
    return this.errorstate;
  }
}

通过这种方式,您可以仅使用 formControl 进行验证。

【讨论】:

    【解决方案3】:

    obsessiveprogrammer 的回答对我来说是正确的,但是我不得不将 childrenEqual 函数与 angular 6 和 strictNullChecks(这是 angular 团队推荐的选项)更改为:

    static childrenEqual: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
            const f = control as FormGroup;
    
            const [firstControlName, ...otherControlNames] = Object.keys(f.controls || {});
    
            if(f.get(firstControlName) == null) {
                return null;
            }
    
            otherControlNames.forEach(controlName => {
                if(f.get(controlName) == null) {
                    return null;
                }
            })
    
            const isValid = otherControlNames.every(controlName => f.get(controlName)!.value === f.get(firstControlName)!.value);
            return isValid ? null : { childrenNotEqual: true };
        }
    

    【讨论】:

      【解决方案4】:

      自定义错误是 Angular Material Forms 的一部分,不需要外部库


      在表单中添加自定义验证器:

      this.form = this.formBuilder.group({
          formField: ['value', this.customValidator.bind(this)]
      });
      

      创建自定义验证器:

      private customValidator(control: FormControl): void {
          if (control.valid) {
              if (someCondition === false) {
                  setTimeout(() => {
                      control.setErrors({ myCustomError: true });
                  });
              }
          }
      }
      

      向您的模板添加错误:

      <!-- As part of the form input -->
      <form [formGroup]="form">
          <mat-form-field>
              <mat-label>Form field</mat-label>
              <input matInput formControlName="formField">
              <mat-error *ngIf="form.get('formField').hasError('myCustomError')">Custom error message</mat-error>
          </mat-form-field>
      </form>
      
      <!-- Outside of the form -->
      <span *ngIf="form.get('formField').hasError('myCustomError')">Custom error message</span>
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-07-29
        • 1970-01-01
        • 2018-12-29
        • 1970-01-01
        • 2018-05-07
        • 2020-01-31
        相关资源
        最近更新 更多