Отображение ошибки пользовательского валидатора с помощью 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 коснулся, отображается моя ошибка:
но, когда я пишу неправильно пароль, ошибка больше не отображается :
сначала я подумал, что неправильно понял использование пользовательского валидатора. Но когда я заменяю на все это, все работает отлично !
заменить ошибку на подсказку
<mat-hint ngxErrors="matchinghPasswordsForm">
<p ngxError="matchingPassword" [when]="['dirty', 'touched']">{{passwordMatchErrorMessage}}</p>
</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
для формы регистрации пользователей. Обратите внимание, что есть два 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'
};
сначала давайте посмотрим на пользовательскую функцию валидатора для группа,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 };
}