使用mat-error显示自定义验证器错误

Pou*_*tte 16 custom-validators angular-material2 angular

我来找你谈论角材料的问题.事实上,我认为这是一个问题,但我更喜欢先找一个误解.

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

用户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)]],
}
Run Code Online (Sandbox Code Playgroud)

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

验证器在这里:

匹配-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;
    }
}
Run Code Online (Sandbox Code Playgroud)

和HTML.

用户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>
Run Code Online (Sandbox Code Playgroud)

我用评论包围了有趣的代码.

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

密码2刚刚触及

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

密码错误2

首先,我以为我误解了自定义验证器的使用.但是,当我更换整个东西工作完美!

通过提示替换错误

<mat-hint ngxErrors="matchinghPasswordsForm">
    <p ngxError="matchingPassword" [when]="['dirty', 'touched']">{{passwordMatchErrorMessage}}</p>
</mat-hint>
Run Code Online (Sandbox Code Playgroud)

使用mat-h​​int标签

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

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

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

提前感谢您的参与时间.

obs*_*mer 49

亚历克斯是对的.您必须使用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>
Run Code Online (Sandbox Code Playgroud)

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

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

组件类

这是一个Angular Component类,它使用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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

组的自定义验证器和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'
};
Run Code Online (Sandbox Code Playgroud)

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

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

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

这种方法中的单行代码非常简单; true如果父控件(我们的FormGroup)无效,则返回(错误存在),但仅在触摸了该字段时返回.这与<mat-error>我们用于表单上其余字段的默认行为一致.

为了将它们组合在一起,我们现在有一个FormGroup,它有一个自定义验证器,当我们的字段不相等时返回错误,并且<mat-error>当组无效时显示.要查看此功能的实际应用,这里有一个工作的plunker,其中包含所提及代码的实现.

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

  • 我不知道为什么它需要这么复杂......为什么不Reactive Forms的团队在通过`FormBuilder`或`FormControl`创建`Form`时只实现提供错误消息作为参数的选项......然后,创建某种全局提供程序,将为“验证”对象配置默认消息。 (4认同)
  • [Mogsdad](/sf/users/117453871/),谢谢提供信息。这是我在 StackOverflow 上的第一个答案,所以我还在学习。我已经查看了链接信息并相应地更新了我的帖子。请让我知道这是否可以接受。 (2认同)
  • reg _没有一个单一的来源给了我完整的答案_您正在处理 Google 的产品 (Angular),预计会有相当多的学习曲线和频繁的中断版本升级。**注意**:AFAIK,推荐的调用`this.createForm()`的方法来自`ngOnInit`而不是`constructor` (2认同)