显示带有垫错误的自定义验证器错误 [英] Display custom validator error with mat-error

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

问题描述

我来找你谈谈角材的问题。实际上,我认为这是一个问题,但是我更喜欢先寻找一个误解。

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

我的兴趣是关于matchPasswordsForm FormGroup。您可以在上面看到验证器。

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

在此处验证器:

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

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

现在,一些解释:带标签,当触摸password2时,显示我的错误:

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

Password2刚刚被触摸

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

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

密码错误2

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

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

通过提示替换错误

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

带有mat-h​​int标签

我希望我很清楚,在发布有关材料设计的问题之前,我真的希望您的观点

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.

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

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 是正确的。您必须使用ErrorStateMatcher。我必须做大量研究才能弄清楚这一点,而且没有一个单一的资料可以给我完整的答案。我不得不将我从多个来源学到的信息拼凑起来,以制定自己的解决方案。希望以下示例可以使您摆脱我的头痛。

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.

表格

这里是使用Angular Material元素进行用户注册页面的表单示例。

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>

如您所见,我正在使用< mat-form-field> ; < input matInput> < mat-error> 标记角材料。我首先想到的是添加 * ngIf 指令来控制< mat-error> 部分的显示时间,但这没有效果!可见性实际上是由< mat-form-field> 的有效性(和触摸状态)控制的,并且没有提供验证器来测试与另一个HTML或Angular中的表单字段。这就是确认字段上的 errorStateMatcher 指令起作用的地方。

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.

errorStateMatcher 指令内置于Angular Material中,并且提供了使用自定义方法确定< mat-form-field> 表单控制,并允许访问父级的有效性状态。为了开始理解如何在该用例中使用errorStateMatcher,让我们首先看一下组件类。

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.

这里是一个Angular Component类,用于使用FormBuilder设置表单的验证。

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

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

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.

组的自定义验证程序和errorStateMatcher指令的组合为我们提供了适当显示确认字段验证错误所需的全部功能。让我们看一下将其整合在一起的自定义验证模块。

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.

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

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

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

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.

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

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.

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

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.

此行中的单行代码方法很简单;如果父控件(我们的FormGroup)无效,则返回 true (存在错误),但仅当该字段已被触摸时才返回。这与< mat-error> 的默认行为保持一致,我们将其用于表单的其余字段。

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.

为了将所有内容整合在一起,我们现在有了一个带有自定义验证器的FormGroup,当我们的字段不相等时,该验证器将返回错误,而< mat-error> 在组无效时显示。要查看此功能的实际效果,请使用以下有效的 plunker ,其中包含上述代码的实现。

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.

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

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆