使用 mat-error 显示自定义验证器错误 [英] Display custom validator error with mat-error

查看:25
本文介绍了使用 mat-error 显示自定义验证器错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我来找你是为了谈论角材料的问题.事实上,我认为这是一个问题,但我更愿意先寻找误会.

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

user-form.component.ts

this.newUserForm = this.fb.group({类型:['',Validators.required],名字:['',Validators.required],姓氏:['', Validators.required],登录: ['', Validators.required],匹配密码表格:this.fb.group({密码 1: ['', Validators.required],密码 2: ['', Validators.required],},{验证器:MatchingPasswordValidator.validate,},),邮件:['', [Validators.required, Validators.pattern(EMAIL_PATTERN)]],cba号码:['411000000',[Validators.required, Validators.pattern(CBANUMBER_PATTERN)],],电话:['', [Validators.required, Validators.pattern(PHONE_PATTERN)]],}

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

这里是验证器:

ma​​tching-password.validator.ts

export class MatchingPasswordValidator {构造函数(){}静态验证(c:FormGroup):验证错误|空值 {if (c.get('password2').value !== c.get('password1').value) {返回{匹配密码:真};}返回空;}}

和 HTML.

user-form.component.html

<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><!-- --><!-- 问题来了--><!-- --><mat-error ngxErrors="matchingPasswordsForm" class="mat-error"><p ngxError="matchingPassword" [when]="['dirty', 'touched']">{{passwordMatchErrorMessage}}</p></mat-error><!-- ^^^^^^^^^^^^^^^^--><!--/问题在这里--><!-- --></mat-form-field>

我用注释包围了有趣的代码.

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

Password2 刚接触

但是,当我写错密码时,错误不再显示:

密码错误2

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

用提示替换错误

<p ngxError="matchingPassword" [when]="['dirty', 'touched']">{{passwordMatchErrorMessage}}</p></mat-h​​int>

带 mat-h​​int 标签

我希望我说的很清楚,在将问题发布到 Material Design github 之前,我真的很想知道您的观点.

如果我误解了什么,请为我错过的东西点燃火焰.

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

提前感谢您抽出宝贵时间.

解决方案

Alex 是正确的.您必须使用 ErrorStateMatcher.我必须进行大量研究才能弄清楚这一点,但没有一个来源可以给我完整的答案.我必须拼凑从多个来源学到的信息,以自己解决问题.希望以下示例能让您免于我所经历的头痛.

表格

这是一个表单示例,该表单将 Angular Material 元素用于用户注册页面.

<mat-form-field><input matInput placeholder="全名";类型=文本"formControlName="fullName"><mat-error>{{errors.fullName}}</mat-error></mat-form-field><div formGroupName="emailGroup"><mat-form-field><input matInput placeholder="Email address";类型=电子邮件"formControlName=电子邮件"><mat-error>{{errors.email}}</mat-error></mat-form-field><mat-form-field><input matInput placeholder="Confirm email address";类型=电子邮件"formControlName="confirmEmail";[errorStateMatcher]=confirmValidParentMatcher"><mat-error>{{errors.confirmEmail}}</mat-error></mat-form-field>

<div formGroupName="passwordGroup"><mat-form-field><input matInput placeholder="密码"类型=密码"formControlName=密码"><mat-error>{{errors.password}}</mat-error></mat-form-field><mat-form-field><input matInput placeholder="确认密码";类型=密码"formControlName=确认密码";[errorStateMatcher]=confirmValidParentMatcher"><mat-error>{{errors.confirmPassword}}</mat-error></mat-form-field>

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

如您所见,我使用了 来自 Angular Material 的标签.我的第一个想法是添加 *ngIf 指令来控制 部分何时出现,但这没有效果!可见性实际上由 <mat-form-field> 的有效性(和触摸"状态)控制,并且没有提供验证器来测试与 HTML 中另一个表单字段的相等性或棱角分明.这就是确认字段上的 errorStateMatcher 指令发挥作用的地方.

errorStateMatcher 指令内置于 Angular Material,并提供使用自定义方法来确定 有效性的能力表单控件,并允许访问父级的有效性状态来这样做.为了开始理解我们如何在这个用例中使用 errorStateMatcher,让我们先来看看组件类.

组件类

这是一个 Angular Component 类,它使用 FormBuilder 为表单设置验证.

export class App {用户注册表格:表格组;ConfirmValidParentMatcher = new ConfirmValidParentMatcher();错误 = 错误消息;构造函数(私有表单构建器:FormBuilder){this.createForm();}创建表单(){this.userRegistrationForm = this.formBuilder.group({全名: ['', [Validators.required,Validators.minLength(1),Validators.maxLength(128)]],emailGroup: this.formBuilder.group({电子邮件: ['', [Validators.required,Validators.email]],确认电子邮件:['',Validators.required]}, { 验证器:CustomValidators.childrenEqual}),密码组:this.formBuilder.group({密码: ['', [Validators.required,Validators.pattern(regExps.password)]],确认密码:['',Validators.required]}, { 验证器:CustomValidators.childrenEqual})});}注册():无效{//API 调用以注册您的用户}}

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

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

自定义验证模块

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

import { FormGroup, FormControl, FormGroupDirective, NgForm, ValidatorFn } from '@angular/forms';从'@angular/material'导入{ErrorStateMatcher};/*** 用于响应式表单验证的自定义验证器函数*/导出类 CustomValidators {/*** 验证表单组中的子控件是否相等*/静态 childrenEqual: ValidatorFn = (formGroup: FormGroup) =>{const [firstControlName, ...otherControlNames] = Object.keys(formGroup.controls || {});const isValid = otherControlNames.every(controlName => formGroup.get(controlName).value === formGroup.get(firstControlName).value);返回有效吗?null : { childrenNotEqual: true };}}/*** 自定义ErrorStateMatcher,当父表单组无效且控件被触摸时返回true(错误存在)*/导出类 ConfirmValidParentMatcher 实现 ErrorStateMatcher {isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {返回 control.parent.invalid &&control.touched;}}/*** 可重用 RegExp 的集合*/export const regExps: { [key: string]: RegExp } = {密码:/^(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]{7,15}$/};/*** 可重用错误信息的集合*/export const errorMessages: { [key: string]: string } = {fullName: '全名必须在 1 到 128 个字符之间',email: '电子邮件必须是有效的电子邮件地址 (username@domain)',ConfirmEmail: '电子邮件地址必须匹配',password: '密码必须在 7 到 15 个字符之间,并且至少包含一个数字和特殊字符',confirmPassword: '密码必须匹配'};

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

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

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

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

总之,我们现在有一个带有自定义验证器的 FormGroup,当我们的字段不相等时返回错误,以及一个 <mat-error> 显示当组是无效的.要查看此功能的实际运行情况,请参阅一个有效的 plunker,其中包含上述代码的实现.

另外,我在博客上写了这个解决方案 这里.

I come to you for talking about a problem with angular material. In fact, I think it's an issue, but I prefer looking for a misunterstanding first.

The first thing about my problem is the context, i try to do a simple form containing two inputs : a password and its' confirmation.

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

My interest is about matchingPasswordsForm FormGroup. You can see the validator on it.

Here the validator:

matching-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;
    }
}

and the 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>

I have surrounded the interesting code with comments.

Now, some explanation : With tag, when password2 is touched, my error is displayed :

Password2 just touched

But, when I write a wrong password, error is not displayed anymore :

Wrong password2

First I thought I was misunderstanding custom validator utilisation. BUT when I replace with the whole thing works perfectly !

replace error by hint

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

With mat-hint tag

I hope I was clear, I really want your point of view before posting an issue on material design github.

If I misunterstood something, please light my fire on what I missed.

A last thing, my tests were done with ngxerrors and *ngif. To be more readable my code sample only use ngxerrors .

Thanks in advance for the time you will take.

解决方案

Alex is correct. You have to use an ErrorStateMatcher. I had to do a lot of research to figure this out, and there wasn’t a single source that gave me the whole answer.  I had to cobble together the information I learned from multiple sources to make my own solution to the problem.  Hopefully the following example will save you from the headache that I experienced.

The Form

Here is an example of a form which uses Angular Material elements for a user registration page.

<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>

As you can see, I am using <mat-form-field>, <input matInput>, and <mat-error> tags from Angular Material. My first thought was to add the *ngIf directive to control when the <mat-error> sections show up, but this has no effect! The visibility is actually controlled by the validity (and "touched" status) of the <mat-form-field>, and there is no provided validator to test equality to another form field in HTML or Angular. That is where the errorStateMatcher directives on the confirmation fields come into play.

The errorStateMatcher directive is built in to Angular Material, and provides the ability to use a custom method to determine the validity of a <mat-form-field> form control, and allows access to the validity status of the parent to do so. To begin to understand how we can use errorStateMatcher for this use case, let's first take a look at the component class.

The Component Class

Here is an Angular Component class that sets up validation for the form using 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
    }
}

The class sets up a FormBuilder for the user registration form. Notice that there are two FormGroups in the class, one for confirming the email address, and one for confirming the password. The individual fields use appropriate validator functions, but both use a custom validator at the group level, which checks to make sure that the fields in each group are equal to each other, and returns a validation error if they are not.

The combination of the custom validator for the groups and the errorStateMatcher directive is what provides us the complete functionality needed to appropriately show validation errors for the confirmation fields. Let's take a look at the custom validation module to bring it all together.

Custom Validation Module

I chose to break the custom validation functionality into its own module, so that it can easily be reused. I also chose to put other things related to my form validation in that module, namely, regular expressions and error messages, for the same reason. Thinking ahead a little, it is likely that you will allow a user to change their email address and password in a user update form as well, right? Here is the code for the entire module.

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'
};

First let's take a look at the custom validator function for the group, CustomValidators.childrenEqual(). Since I come from an object-oriented programming background, I chose to make this function a static class method, but you could just as easily make it a standalone function. The function must be of type ValidatorFn (or the approprate literal signature), and take a single parameter of type AbstractControl, or any derivative type. I chose to make it FormGroup, since that is the use case it's for.

The function's code iterates over all of the controls in the FormGroup, and ensures that their values all equal that of the first control. If they do, it returns null (indicates no errors), otherwise is returns a childrenNotEqual error.

So now we have an invalid status on the group when the fields are not equal, but we still need to use that status to control when to show our error message. Our ErrorStateMatcher, ConfirmValidParentMatcher, is what can do this for us. The errorStateMatcher directive requires that you point to an instance of a class which implements the provided ErrorStateMatcher class in Angular Material. So that is the signature used here. ErrorStateMatcher requires the implementation of an isErrorState method, with the signature shown in the code. It returns true or false; true indicates that an error exists, which makes the input element's status invalid.

The single line of code in this method is quite simple; it returns true (error exists) if the parent control (our FormGroup) is invalid, but only if the field has been touched. This aligns with the default behavior of <mat-error>, which we are using for the rest of the fields on the form.

To bring it all together, we now have a FormGroup with a custom validator that returns an error when our fields are not equal, and a <mat-error> which displays when the group is invalid. To see this functionality in action, here is a working plunker with an implementation of the code mentioned.

Also, I've blogged this solution here.

这篇关于使用 mat-error 显示自定义验证器错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
其他开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆