Обработка ошибок формы с использованием компонентов Angular - TypeScript
В настоящее время я работаю над формой в Angular/Typescript из нескольких полей (более 10 полей), и я хотел более корректно управлять ошибками, не дублируя код на моей html-странице.
Вот пример формы:
<form [formGroup]="myForm">
<label>Name</label>
<input type="text" formControlName="name">
<p class="error_message" *ngIf="myForm.get('name').invalid && (myForm.submitted || myForm.get('name').dirty)">Please provide name</p>
<label>Lastname</label>
<input type="text" formControlName="lastname">
<p class="error_message" *ngIf="myForm.get('lastname').invalid && (myForm.submitted || myForm.get('lastname').dirty)">Please provide email</p>
<label>Email</label>
<input type="text" formControlName="email">
<p class="error_message" *ngIf="myForm.get('email').hasError('required') && (myForm.submitted || myForm.get('email').dirty)">Please provide email</p>
<p class="error_message" *ngIf="myForm.get('email').hasError('email') && (myForm.submitted || myForm.get('email').dirty)">Please provide valid email</p>
</form>
Ответы
Ответ 1
Вы можете переместить ошибки проверки в компонент и передать в formControl.errors в качестве свойства ввода. Таким образом, все сообщения проверки могут быть повторно использованы. Вот пример на StackBlitz. Код использует угловой материал, но все же должен быть удобным, даже если вы этого не сделали.
валидация-errors.component.ts
import { Component, OnInit, Input, ChangeDetectionStrategy } from '@angular/core';
import { FormGroup, ValidationErrors } from '@angular/forms';
@Component({
selector: 'validation-errors',
templateUrl: './validation-errors.component.html',
styleUrls: ['./validation-errors.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ValidationErrorsComponent implements OnInit {
@Input() errors: ValidationErrors;
constructor() {}
ngOnInit() {}
}
валидация-errors.component.html
<ng-container *ngIf="errors && errors['required']"> Required</ng-container>
<ng-container *ngIf="errors && errors['notUnique']">Already exists</ng-container>
<ng-container *ngIf="errors && errors['email']">Please enter a valid email</ng-container>
Для сообщений проверки назад задайте ошибку вручную в элементе управления формой.
const nameControl = this.userForm.get('name');
nameControl.setErrors({
"notUnique": true
});
Чтобы использовать компонент проверки в форме:
<form [formGroup]="userForm" (ngSubmit)="submit()">
<mat-form-field>
<input matInput placeholder="name" formControlName="name" required>
<mat-error *ngIf="userForm.get('name').status === 'INVALID'">
<validation-errors [errors]="userForm.get('name').errors"></validation-errors>
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="email" formControlName="email" required>
<mat-error *ngIf="userForm.get('email').status === 'INVALID'">
<validation-errors [errors]="userForm.get('email').errors"></validation-errors>
</mat-error>
</mat-form-field>
<button mat-raised-button class="mat-raised-button" color="accent">SUBMIT</button>
</form>
Ответ 2
демонстрация
Вы можете ввести NgForm
и получить доступ к директиве FormControlName
через @ContentChild
в рамках настраиваемого компонента @ContentChild
подлинности для достижения повторного использования:
@Component({
selector: '[validator]',
template: '
<ng-content></ng-content>
<div *ngIf="formControl.invalid">
<div *ngIf="formControl.errors.required && (form.submitted || formControl.dirty)">
Please provide {{ formControl.name }}
</div>
<div *ngIf="formControl.errors.email && (form.submitted || formControl.dirty)">
Please provide a valid email
</div>
<div *ngIf="formControl.errors.notstring && (form.submitted || formControl.dirty)">
Invalid name
</div>
</div>
'})
export class ValidatorComponent implements OnInit {
@ContentChild(FormControlName) formControl;
constructor(private form: NgForm) {
}
ngOnInit() { }
}
Чтобы использовать его, вы должны поместить все элементы управления формы (который имеет formControlName) с HTML-элементом и добавить атрибут проверки:
<form #f="ngForm" (ngSubmit)="onSubmit(f)" novalidate>
<div [formGroup]="myForm">
<label>Name</label>
<div validator>
<input type="text" formControlName="name">
</div>
<label>Lastname</label>
<div validator>
<input type="text" formControlName="lastname">
</div>
<label>Email</label>
<div validator>
<input type="text" formControlName="email">
</div>
</div>
<button type="submit">Submit</button>
</form>
Это будет работать для синхронных и асинхронных валидаторов.
Ответ 3
У меня было такое же требование, никто не любит повторно писать тот же код дважды.
Это можно сделать, создав настраиваемые элементы управления формой. Идея заключается в том, что вы создаете свои пользовательские элементы управления формой, имеете общую службу, которая создает собственный объект formControl и вводит соответствующие валидаторы на основе типа данных, предоставленного в объект FormControl.
Откуда появился тип данных?
Имейте файл в своих активах или в любом месте, где есть такие типы:
[{
"nameType" : {
maxLength : 5 ,
minLength : 1 ,
pattern : xxxxxx,
etc
etc
}
}
]
Это вы можете прочитать в службе ValidatorService
и выбрать соответствующий тип данных, с помощью которого вы можете создать свои валидаторы и вернуться к своему пользовательскому формату.
Например,
<ui-text name="name" datatype="nameType" [(ngModel)]="data.name"></ui-text>
Это краткое описание этого на высоком уровне того, что я сделал для достижения этого. Если вам нужна дополнительная информация, сделайте комментарий. Я вышел, поэтому я не могу предоставить вам базу кода прямо сейчас, но завтра может обновить ответ.
ОБНОВЛЕНИЕ для части, отображающей ошибку
Вы можете сделать 2 вещи для этого, связать свой валидатор formControl с div внутри элемента управления и переключить его с помощью *ngIf="formControl.hasError('required
)" и т.д.
Чтобы сообщение/ошибка отображалась в другом родовом месте, таком как Доска сообщений, лучше разместить эту разметку в BoardentComponent, которая не удаляется во время маршрутизации (зависит от требования) и заставляет этот компонент прослушивать событие MessageEmit который ваш ErrorStateMatcher
вашего formControl будет запускаться в случае необходимости (на основе требования).
Это дизайн, который мы использовали, и он работал очень хорошо, вы можете много сделать с этими formControls, как только вы начнете их настраивать.
Ответ 4
Вы можете создать настраиваемый компонент ValidationMessagesComponent
:
Шаблон:
<p class="error_message" *ngIf="form.get(controlName).hasError('required') && (form.submitted || form.get(controlName).dirty)">Please provide {{controlName}}</p>
<p class="error_message" *ngIf="form.get(controlName).hasError('email') && (form.submitted || form.get(controlName).dirty)">Please provide valid {{controlName}}</p>
...other errors
И с входами:
@Input() controlName;
@Input() form;
Затем используйте его следующим образом:
<validation-messages [form]="myForm" controlName="email"></validation-messages>
Ответ 5
Для проверки html я написал бы customformcontrol, который будет в основном быть оберткой вокруг ввода. Я бы также написал специальные валидаторы, которые возвращают сообщение об ошибке (встроенные валидаторы возвращают объект, который я считаю). В вашем пользовательском formcontrol вы можете сделать что-то вроде этого:
<div *ngIf="this.formControl.errors">
<p>this.formControl.errors?.message</p>
</div>
Для проверки подлинности на бэкэнд вы можете написать асинхронный валидатор.
Ответ 6
Вы можете использовать это репо, которое имеет сообщения о проверке по умолчанию, и вы также можете их настроить
пример использования будет таким
<form [formGroup]="editorForm" novalidate>
<label>First Name</label>
<input formControlName="firstName" type="text">
<ng2-mdf-validation-message [control]="firstName" *ngIf="!firstName.pristine"></ng2-mdf-validation-message>
</form>
Ответ 7
Чтобы очистить код шаблона и избежать дублирования кода проверки сообщений, мы должны изменить их, чтобы их можно было повторно использовать, создавая настраиваемую директиву, которая добавляет и удаляет проверку блока кода сообщения, является опцией (показано ниже в демонстрации).
Показать/скрыть проверочные сообщения
В директиве мы можем получить доступ к директиве "управление хостом" и добавить/удалить подтверждающее сообщение на основе подтверждения его статуса, подписавшись на это событие valueChanges
.
@Directive(...)
export class ValidatorMessageDirective implements OnInit {
constructor(
private container: ControlContainer,
private elem: ElementRef, // host dom element
private control: NgControl // host form control
) { }
ngOnInit() {
const control = this.control.control;
control.valueChanges.pipe(distinctUntilChanged()).subscribe(() => {
this.option.forEach(validate => {
if (control.hasError(validate.type)) {
const validateMessageElem = document.getElementById(validate.id);
if (!validateMessageElem) {
const divElem = document.createElement('div');
divElem.innerHTML = validate.message;
divElem.id = validate.id;
this.elem.nativeElement.parentNode.insertBefore(divElem, this.elem.nativeElement.nextSibling);
}
} else {
const validateMessageElem = document.getElementById(validate.id);
if (validateMessageElem) {
this.elem.nativeElement.parentNode.removeChild(validateMessageElem);
}
}
})
});
}
}
Проверить параметры
Директива добавляет и удаляет подтверждение сообщения на основе соответствующих ошибок проверки. Таким образом, последний шаг, который мы должны сделать, - это указать директиву, какие типы проверочных ошибок смотреть и какие сообщения следует показывать, что поле @Input
помощью которого мы переносим проверку параметров директивы.
Затем мы можем просто написать код шаблона, как показано ниже:
<form [formGroup]="form">
<input type="text" formControlName="test" [validate-message]="testValidateOption"><br/>
<input type="number" formControlName="test2" [validate-message]="test2ValidateOption">
</form>
См. Рабочую демонстрацию.
Ответ 8
Лучший способ - реализовать пользовательский ControlValueAccessor
для каждого типа ввода, объединив <label>
, <input>
и некоторые теги для отображения сообщения об ошибке (в моем проекте я просто использую атрибут title
для этой цели) в одном компоненте.
Все атрибуты доступа должны реализовывать один и тот же интерфейс или расширять базовый абстрактный класс, предоставляя методы для установки и очистки сообщения об ошибке и любых других методов, которые вы можете вызвать из директив валидатора.
Кроме того, вам потребуется реализовать специальные директивы валидатора для каждого типа проверки (я должен был повторно реализовать даже required
и maxlength
), валидаторы должны возвращать объекты ошибок в равномерном порядке, то есть для проверки подлинности электронной почты {email: "Invalid email address"}
. Директивы валидатора могут получить ссылку на ваши контрольные значения с помощью инъекций - @Inject(NG_VALUE_ACCESSOR) controls:AbstractFormComponent<any>[]
(обычно массив с одним элементом, AbstractFormComponent
- ваш базовый класс для аксессуаров), используйте эту ссылку для установки или очистки доступа сообщение об ошибке.
Вы также можете реализовать два дополнительных типа директив валидатора: sync и async, которые могут получать функцию валидатора через @Input
т. @Input
[async]="loginValidatorFn"
@Input
[async]="loginValidatorFn"
, где loginValidatorFn
определяется в классе компонента и возвращает Observable<ValidationErrors>
.
Это реальный код из нашего приложения:
<div class="input" [caption]="'SSN: '" name="ssn" type="text" [(ngModel)]="item.ssn" [async]="memberSsnValidatorFn" required></div>
Ответ 9
Вот некоторая часть кода, который я использовал в библиотеке для создания динамических форм.
Это FormError.ts
который используется для получения ошибок и пользовательских сообщений, если мы хотим.
import { AbstractControl } from "@angular/forms";
type ErrorFunction = (errorName: string, error: object) => string;
export type ErrorGetter =
string | { [key2: string]: string } | ErrorFunction;
export class FormError {
constructor(private errorGetter?: ErrorGetter) { }
hasError(abstractControl: AbstractControl) {
return abstractControl.errors && (abstractControl.dirty || abstractControl.touched);
}
getErrorMsgs(abstractControl: AbstractControl): string[] {
if (!this.hasError(abstractControl))
return null;
let errors = abstractControl.errors;
return Object.keys(errors).map(anyError => this.getErrorValue(anyError, errors[anyError]));
}
getErrorValue(errorName: string, error: object): string {
let errorGetter = this.errorGetter;
if (!errorGetter)
return predictError(errorName, error);
if (isString(errorGetter))
return errorGetter;
else if (isErrorFunction(errorGetter)) {
let errorString = errorGetter(errorName, error);
return this.predictedErrorIfEmpty(errorString, errorName, error)
}
else {
let errorString = this.errorGetter[errorName];
return this.predictedErrorIfEmpty(errorString, errorName, error)
}
}
predictedErrorIfEmpty(errorString: string, errorName: string, error: object) {
if (errorString == null || errorString == undefined)
return predictError(errorName, error);
return errorString;
}
}
function predictError(errorName: string, error: object): string {
if (errorName === 'required')
return 'Cannot be blank';
if (errorName === 'min')
return 'Should not be less than ${error['min']}';
if (errorName === 'max')
return 'Should not be more than ${error['max']}';
if (errorName === 'minlength')
return 'Alteast ${error['requiredLength']} characters';
if (errorName === 'maxlength')
return 'Atmost ${error['requiredLength']} characters';
// console.warn('Error for ${errorName} not found. Error object = ${error}');
return 'Error';
}
export function isString(s: any): s is string {
return typeof s === 'string' || s instanceof String;
}
export function isErrorFunction(f: any): f is ErrorFunction {
return typeof f === "function";
}
Пользовательские сообщения
class FormError {
constructor(private errorGetter?: ErrorGetter) { }
}
Теперь ErrorGetter
похож на
type ErrorFunction = (errorName: string, error: object) => string;
type ErrorGetter =
string | { [key2: string]: string } | ErrorFunction;
-
Если нам нужна постоянная ошибка для любой ошибки, то это должно быть похоже на
new FormError('Password is not right')
-
Если нам нужна постоянная ошибка для конкретной ошибки, это должно быть похоже на
new FormError({required:'Address is necessary.'})
Для других ошибок это приведет к ошибке предсказания.
-
Если мы хотим использовать функцию для конкретной ошибки, это должно быть похоже на
new FormError((errorName,errorObject)=>{ if(errorName=='a') return '2';})
Для других ошибок это приведет к ошибке предсказания.
-
Измените функцию predError в соответствии с вашими потребностями.
Компонент FormError
form-error.html
<ng-container *ngIf="formError.hasError(control)">
<div class='form-error-message' *ngFor='let error of formError.getErrorMsgs(control)'>{{error}}</div>
</ng-container>
form-error.scss
form-error {
.form-error-message {
color: red;
font-size: .75em;
padding-left: 16px;
}
}
form-error.ts
@Component({
selector: 'form-error',
templateUrl: 'form-error.html'
})
export class FormErrorComponent {
@Input() formError: FromError;
@Input() control: AbstractControl;
}
использование
<form-error [control]='thatControl' ></form-error>
Очевидно, что FormError
не лучший дизайн. Измените, как вам нравится.