Отображение ошибки пользовательского валидатора с помощью mat-error

Я пришел к вам, чтобы поговорить о проблеме с угловым материалом. На самом деле, я думаю, что это проблема, но я предпочитаю сначала искать неправильное понимание.

первое, что касается моей проблемы, это контекст, я пытаюсь сделать простую форму, содержащую два входа: пароль и его подтверждение.

пользователей-форма.деталь.ТС

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

мой интерес заключается в matchingPasswordsForm FormGroup. Вы можете увидеть валидатор на он.

здесь валидатор:

соответствующий пароль.валидатор.ТС

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.

пользователей-форма.деталь.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>

я окружил интересный код комментариями.

теперь, некоторое объяснение : с тегом, когда password2 коснулся, отображается моя ошибка:

Password2 только что коснулся

но, когда я пишу неправильно пароль, ошибка больше не отображается :

неправильный password2

сначала я подумал, что неправильно понял использование пользовательского валидатора. Но когда я заменяю на все это, все работает отлично !

заменить ошибку на подсказку

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

с тегом mat-hint

Я надеюсь, что я был ясен, я действительно хочу, чтобы ваша точка зрения, прежде чем размещать вопрос о material design на GitHub.

если я что-то misunterstood, пожалуйста, свет мой огонь, что я пропустила.

и последнее, мои тесты были сделаны с ngxerrors и *ngif. Чтобы быть более читаемым, мой образец кода использует только ngxerrors .

заранее спасибо за время, которое вы будете принимать.

2 ответов


Алекс является правильным. Вы должны использовать ErrorStateMatcher. Мне пришлось провести много исследований, чтобы понять это, и не было ни одного источника, который дал бы мне полный ответ.  Мне пришлось собрать информацию, которую я узнал из нескольких источников, чтобы сделать свое собственное решение проблемы.  Надеюсь, следующий пример спасет вас от головной боли, которую я испытал.

Форму

вот пример формы который использует элементы углового материала для страницы регистрации пользователя.

<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 директивы на полях подтверждения вступают в игру.

на errorStateMatcher директива встроена в угловой материал и предоставляет возможность использовать пользовательский метод для определения действительности <mat-form-field> управление формой и позволяет получить доступ к статусу действительности родителя для этого. Чтобы начать понимать, как мы можем использовать errorStateMatcher для этого случая использования, давайте сначала рассмотрим класс component.

Компонент Класс!--36-->

вот угловой класс компонента, который устанавливает проверку формы с помощью 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 (или буквенная подпись approprate) и возьмите один параметр типа AbstractControl, или любой производный тип. Я решил сделать это FormGroup, так как это вариант использования, для которого он предназначен.

код функции повторяет все элементы управления в FormGroup, и гарантирует, что их значения равны значениям первого элемента управления. Если они это сделают, он вернется null (без ошибок), иначе возвращает childrenNotEqual ошибка.

таким образом, теперь у нас есть недопустимый статус в группе, когда поля не равны, но нам все равно нужно использовать этот статус для управления, когда показывать наше сообщение об ошибке. Наш ErrorStateMatcher,ConfirmValidParentMatcher, что может сделать это для нас. Директива errorStateMatcher требует указания на экземпляр класса который реализует предоставленный класс ErrorStateMatcher в угловом материале. Так вот какая здесь используется подпись. ErrorStateMatcher требует реализации isErrorState метод, с подписью, показанной в коде. Он возвращается true или false; true указывает на наличие ошибки, что делает недопустимым состояние входного элемента.

одиночная строка кода в этом методе довольно проста; она возвращает true (ошибка существует), если родительский элемент управления (наша FormGroup) недопустимо, но только если поле было затронуто. Это согласуется с поведением по умолчанию <mat-error>, который мы используем для остальных полей формы.

чтобы объединить все это вместе, теперь у нас есть FormGroup с пользовательским валидатором, который возвращает ошибку, когда наши поля не равны, и <mat-error>, который отображается, когда группа является недействительным. Чтобы увидеть эту функциональность в действии, вот рабочий plunker С реализацией кода упомянутый.

кроме того, я написал в блоге это решение здесь.


obsessiveprogrammerответ был правильным для меня, однако мне пришлось изменить childrenEqual функция с угловым 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 };
    }