Проверка данных формы
Проверка точности и полноты пользовательского ввода позволяет повысить общее качество данных. На этой странице показано, как проверять пользовательский ввод в интерфейсе и отображать полезные сообщения об ошибках как в реактивных формах, так и в формах, управляемых шаблонами.
Проверка данных в формах, управляемых шаблонами
Чтобы добавить проверку в форму, управляемую шаблоном, добавьте те же атрибуты проверки, что и при использовании встроенной проверки форм HTML. Angular использует директивы, чтобы сопоставлять эти атрибуты с функциями-валидаторами в платформе.
При каждом изменении значения элемента управления формы Angular запускает проверку и формирует либо список ошибок проверки, в результате чего устанавливается статус INVALID, либо null, в результате чего устанавливается статус VALID.
Затем можно проверить состояние элемента управления, экспортировав ngModel в локальную переменную шаблона. В следующем примере NgModel экспортируется в переменную с именем name:
<input
type="text"
id="name"
name="name"
class="form-control"
required
minlength="4"
appForbiddenName="bob"
[(ngModel)]="actor.name"
#name="ngModel"
/>
@if (name.invalid && (name.dirty || name.touched)) {
<div class="alert">
@if (name.hasError('required')) {
<div>Name is required.</div>
}
@if (name.hasError('minlength')) {
<div>Name must be at least 4 characters long.</div>
}
@if (name.hasError('forbiddenName')) {
<div>Name cannot be Bob.</div>
}
</div>
}
Обратите внимание на следующие особенности, показанные в примере.
-
Элемент
<input>содержит атрибуты проверки HTML:requiredиminlength. Он также содержит пользовательскую директиву-валидаторforbiddenName. Дополнительные сведения см. в разделе Пользовательские валидаторы. -
#name="ngModel"экспортируетNgModelв локальную переменную с именемname.NgModelотражает многие свойства базового экземпляраFormControl, поэтому его можно использовать в шаблоне для проверки состояния элемента управления, напримерvalidиdirty. Полный список свойств элемента управления см. в справочнике API AbstractControl.-
Внешний элемент
@ifпоказывает набор вложенных сообщений, только еслиnameнедействителен и элемент управления находится в состоянииdirtyилиtouched. -
Каждый вложенный элемент
@ifможет отображать пользовательское сообщение для одной из возможных ошибок проверки. Предусмотрены сообщения дляrequired,minlengthиforbiddenName.
-
ПОЛЕЗНО: Чтобы валидатор не отображал ошибки до того, как пользователь сможет отредактировать форму, следует проверять состояние элемента управления: dirty или touched.
- Когда пользователь изменяет значение отслеживаемого поля, элемент управления помечается как «dirty» (изменённый).
- Когда пользователь переводит фокус с элемента управления формы, он помечается как «touched» (затронутый).
Проверка данных в реактивных формах
В реактивной форме источником достоверных данных является класс компонента. Вместо добавления валидаторов с помощью атрибутов в шаблоне вы добавляете функции-валидаторы непосредственно в модель элемента управления формы в классе компонента. Затем Angular вызывает эти функции при каждом изменении значения элемента управления.
Функции-валидаторы
Функции-валидаторы могут быть синхронными или асинхронными.
| Тип валидатора | Подробности |
|---|---|
| Синхронные валидаторы | Синхронные функции, принимающие экземпляр элемента управления и немедленно возвращающие набор ошибок проверки или null. Передайте их вторым аргументом при создании FormControl. |
| Асинхронные валидаторы | Асинхронные функции, принимающие экземпляр элемента управления и возвращающие Promise или Observable, который позднее выдаёт набор ошибок проверки или null. Передайте их третьим аргументом при создании FormControl. |
Из соображений производительности Angular запускает асинхронные валидаторы, только если все синхронные валидаторы пройдены. Каждый валидатор должен завершить работу, прежде чем будут установлены ошибки.
Встроенные функции-валидаторы
Вы можете написать собственные функции-валидаторы или воспользоваться некоторыми встроенными валидаторами Angular.
Те же встроенные валидаторы, которые доступны в виде атрибутов в формах, управляемых шаблонами, например required и minlength, доступны и в виде функций класса Validators. Полный список встроенных валидаторов см. в справочнике API Validators.
Чтобы преобразовать форму актёра в реактивную форму, используйте некоторые из тех же встроенных валидаторов — на этот раз в виде функций, как показано в следующем примере.
actorForm = new FormGroup({
name: new FormControl(this.actor.name, [
Validators.required,
Validators.minLength(4),
forbiddenNameValidator(/bob/i), // <-- Here's how you pass in the custom validator.
]),
role: new FormControl(this.actor.role),
skill: new FormControl(this.actor.skill, Validators.required),
});
get name() {
return this.actorForm.get('name');
}
get skill() {
return this.actorForm.get('skill');
}
В этом примере для элемента управления name задаются два встроенных валидатора — Validators.required и Validators.minLength(4) — и один пользовательский валидатор, forbiddenNameValidator.
Все эти валидаторы синхронные, поэтому они передаются вторым аргументом. Обратите внимание, что для поддержки нескольких валидаторов можно передать функции в виде массива.
В этом примере также добавлено несколько методов-геттеров. В реактивной форме всегда можно получить доступ к любому элементу управления через метод get родительской группы, но иногда удобно определить геттеры как сокращённую запись для шаблона.
Если снова посмотреть на шаблон для поля ввода name, он окажется довольно похожим на пример с формой, управляемой шаблоном.
<input type="text" id="name" class="form-control" formControlName="name" required />
@if (name.invalid && (name.dirty || name.touched)) {
<div class="alert alert-danger">
@if (name.hasError('required')) {
<div>Name is required.</div>
}
@if (name.hasError('minlength')) {
<div>Name must be at least 4 characters long.</div>
}
@if (name.hasError('forbiddenName')) {
<div>Name cannot be Bob.</div>
}
</div>
}
Эта форма отличается от версии, управляемой шаблоном, тем, что больше не экспортирует никакие директивы. Вместо этого она использует геттер name, определённый в классе компонента.
Обратите внимание, что атрибут required по-прежнему присутствует в шаблоне. Хотя для проверки он не требуется, его следует сохранить для обеспечения доступности.
Определение пользовательских валидаторов
Встроенные валидаторы не всегда точно соответствуют конкретным требованиям приложения, поэтому иногда необходимо создать пользовательский валидатор.
Рассмотрим функцию forbiddenNameValidator из предыдущего примера. Вот как выглядит её определение.
/** An actor's name can't match the given regular expression */
export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const forbidden = nameRe.test(control.value);
return forbidden ? {forbiddenName: {value: control.value}} : null;
};
}
Эта функция — фабрика, которая принимает регулярное выражение для обнаружения определённого запрещённого имени и возвращает функцию-валидатор.
В этом примере запрещённое имя — «bob», поэтому валидатор отклоняет любое имя актёра, содержащее «bob». В другом случае он может отклонять «alice» или любое имя, соответствующее настроенному регулярному выражению.
Фабрика forbiddenNameValidator возвращает настроенную функцию-валидатор. Эта функция принимает объект элемента управления Angular и возвращает либо null, если значение элемента управления допустимо, либо объект ошибки проверки. Обычно объект ошибки проверки содержит свойство с именем ключа проверки — 'forbiddenName', — а его значением служит произвольный словарь, значения которого можно вставить в сообщение об ошибке, {name}.
Пользовательские асинхронные валидаторы похожи на синхронные, но должны возвращать Promise или Observable, который позднее выдаёт null или объект ошибки проверки. В случае Observable он должен завершиться, после чего форма использует последнее выданное значение для проверки.
Добавление пользовательских валидаторов в реактивные формы
В реактивных формах добавьте пользовательский валидатор, передав функцию непосредственно в FormControl.
actorForm = new FormGroup({
name: new FormControl(this.actor.name, [
Validators.required,
Validators.minLength(4),
forbiddenNameValidator(/bob/i), // <-- Here's how you pass in the custom validator.
]),
role: new FormControl(this.actor.role),
skill: new FormControl(this.actor.skill, Validators.required),
}); Добавление пользовательских валидаторов в формы, управляемые шаблонами
В формах, управляемых шаблонами, добавьте в шаблон директиву, которая оборачивает функцию-валидатор. Например, соответствующая ForbiddenValidatorDirective служит оболочкой для forbiddenNameValidator.
Angular распознаёт роль директивы в процессе проверки, потому что директива регистрируется у провайдера NG_VALIDATORS, как показано в следующем примере. NG_VALIDATORS — это предопределённый провайдер с расширяемой коллекцией валидаторов.
providers: [
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => ForbiddenValidatorDirective),
multi: true,
},
],
})
export class ForbiddenValidatorDirective implements Validator {
readonly forbiddenName = input<string>('', {alias: 'appForbiddenName'});
validate(control: AbstractControl): ValidationErrors | null {
return this.forbiddenName
? forbiddenNameValidator(new RegExp(this.forbiddenName(), 'i'))(control)
: null;
}
}
Затем класс директивы реализует интерфейс Validator, чтобы обеспечить простую интеграцию с формами Angular. Ниже приведён оставшийся код директивы, который поможет понять, как всё это работает вместе.
@Directive({
selector: '[appForbiddenName]',
providers: [
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => ForbiddenValidatorDirective),
multi: true,
},
],
})
export class ForbiddenValidatorDirective implements Validator {
readonly forbiddenName = input<string>('', {alias: 'appForbiddenName'});
validate(control: AbstractControl): ValidationErrors | null {
return this.forbiddenName
? forbiddenNameValidator(new RegExp(this.forbiddenName(), 'i'))(control)
: null;
}
}
Когда ForbiddenValidatorDirective будет готова, можно добавить её селектор appForbiddenName к любому элементу ввода, чтобы активировать её. Например:
<input
type="text"
id="name"
name="name"
class="form-control"
required
minlength="4"
appForbiddenName="bob"
[(ngModel)]="actor.name"
#name="ngModel"
/>
ПОЛЕЗНО: Обратите внимание, что пользовательская директива проверки создаётся с помощью useExisting, а не useClass. Зарегистрированный валидатор должен быть именно этим экземпляром ForbiddenValidatorDirective — экземпляром в форме, у которого свойство forbiddenName привязано к значению «bob».
Если заменить useExisting на useClass, будет зарегистрирован новый экземпляр класса, у которого нет forbiddenName.
CSS-классы состояния элементов управления
Angular автоматически отражает многие свойства элементов управления в виде CSS-классов элемента формы. Используйте эти классы, чтобы задавать стили элементам управления формы в соответствии с её состоянием. Сейчас поддерживаются следующие классы.
.ng-valid.ng-invalid.ng-pending.ng-pristine.ng-dirty.ng-untouched.ng-touched-
.ng-submitted(только для внешнего элемента формы)
В следующем примере в форме актёра классы .ng-valid и .ng-invalid используются для задания цвета границ элементов управления формы.
.ng-valid[required], .ng-valid.required {
border-left: 5px solid #42A948; /* green */
}
.ng-invalid:not(form) {
border-left: 5px solid #a94442; /* red */
}
.alert div {
background-color: #fed3d3;
color: #820000;
padding: 1rem;
margin-bottom: 1rem;
}
.form-group {
margin-bottom: 1rem;
}
label {
display: block;
margin-bottom: .5rem;
}
select {
width: 100%;
padding: .5rem;
}
Проверка нескольких полей
Валидатор нескольких полей — это пользовательский валидатор, который сравнивает значения разных полей формы и принимает или отклоняет их в совокупности. Например, в форме могут быть взаимоисключающие варианты: пользователь может выбрать A или B, но не оба сразу. Значения одних полей также могут зависеть от других: например, пользователю может быть разрешено выбрать B, только если выбран вариант A.
В следующих примерах перекрёстной проверки показано, как выполнить следующие действия:
- Проверить ввод в реактивной форме или форме, управляемой шаблоном, на основе значений двух соседних элементов управления;
- Показать понятное сообщение об ошибке после взаимодействия пользователя с формой и неудачного прохождения проверки.
В примерах перекрёстная проверка используется для того, чтобы актёры не повторяли одно и то же имя в разных ролях при заполнении формы актёра. Валидаторы проверяют, что имена актёров не совпадают с названиями ролей.
Добавление перекрёстной проверки в реактивные формы
Форма имеет следующую структуру:
const actorForm = new FormGroup({
'name': new FormControl(),
'role': new FormControl(),
'skill': new FormControl(),
});
Обратите внимание, что name и role — соседние элементы управления. Чтобы проверить оба элемента управления в одном пользовательском валидаторе, необходимо выполнить проверку в общем родительском элементе управления: FormGroup. Получите дочерние элементы управления из FormGroup, чтобы сравнить их значения.
Чтобы добавить валидатор в FormGroup, передайте новый валидатор вторым аргументом при создании.
const actorForm = new FormGroup(
{
'name': new FormControl(),
'role': new FormControl(),
'skill': new FormControl(),
},
{validators: unambiguousRoleValidator},
);
Код валидатора выглядит следующим образом.
/** An actor's name can't match the actor's role */
export const unambiguousRoleValidator: ValidatorFn = (
control: AbstractControl,
): ValidationErrors | null => {
const name = control.get('name');
const role = control.get('role');
return name && role && name.value === role.value ? {unambiguousRole: true} : null;
};
Валидатор unambiguousRoleValidator реализует интерфейс ValidatorFn. Он принимает объект элемента управления Angular в качестве аргумента и возвращает null, если форма действительна, или ValidationErrors в противном случае.
Валидатор получает дочерние элементы управления, вызывая метод FormGroup get, а затем сравнивает значения элементов управления name и role.
Если значения не совпадают, роль однозначна, оба элемента действительны, и валидатор возвращает null. Если значения совпадают, роль актёра неоднозначна, поэтому валидатор должен пометить форму как недействительную, вернув объект ошибки.
Чтобы улучшить взаимодействие с пользователем, шаблон отображает соответствующее сообщение об ошибке, когда форма недействительна.
@if (actorForm.hasError('unambiguousRole') && (actorForm.touched || actorForm.dirty)) {
<div class="cross-validation-error-message alert alert-danger">
Name cannot match role or audiences will be confused.
</div>
}
Этот @if отображает ошибку, если FormGroup содержит ошибку перекрёстной проверки, возвращённую валидатором unambiguousRoleValidator, но только если пользователь завершил взаимодействие с формой.
Добавление перекрёстной проверки в формы, управляемые шаблонами
Для формы, управляемой шаблоном, необходимо создать директиву-обёртку для функции-валидатора. Укажите эту директиву в качестве валидатора с помощью токена NG_VALIDATORS, как показано в следующем примере.
@Directive({
selector: '[appUnambiguousRole]',
providers: [
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => UnambiguousRoleValidatorDirective),
multi: true,
},
],
})
export class UnambiguousRoleValidatorDirective implements Validator {
validate(control: AbstractControl): ValidationErrors | null {
return unambiguousRoleValidator(control);
}
}
Необходимо добавить новую директиву в HTML-шаблон. Поскольку валидатор должен быть зарегистрирован на самом верхнем уровне формы, в следующем шаблоне директива добавляется к тегу form.
<form #actorForm="ngForm" appUnambiguousRole>
Чтобы улучшить взаимодействие с пользователем, при недействительной форме отображается соответствующее сообщение об ошибке.
@if (actorForm.hasError('unambiguousRole') && (actorForm.touched || actorForm.dirty)) {
<div class="cross-validation-error-message alert">Name cannot match role.</div>
}
Это одинаково работает как в формах, управляемых шаблонами, так и в реактивных формах.
Создание асинхронных валидаторов
Асинхронные валидаторы реализуют интерфейсы AsyncValidatorFn и AsyncValidator. Они очень похожи на синхронные аналоги, но имеют следующие отличия.
- Функции
validate()должны возвращать Promise или Observable; - Возвращаемый Observable должен быть конечным, то есть в какой-то момент завершиться. Чтобы преобразовать бесконечный Observable в конечный, передайте его через оператор фильтрации, например
first,last,takeилиtakeUntil.
Асинхронная проверка выполняется после синхронной и только в том случае, если синхронная проверка прошла успешно. Благодаря этому формы могут избегать потенциально затратных асинхронных проверок (например, HTTP-запроса), если более простые методы проверки уже обнаружили недопустимые данные.
После начала асинхронной проверки элемент управления формы переходит в состояние pending. Проверяйте свойство pending элемента управления и используйте его, чтобы визуально сообщать о ходе проверки.
Распространённый шаблон интерфейса — показывать индикатор загрузки во время асинхронной проверки. В следующем примере показано, как реализовать это в форме, управляемой шаблоном.
<input [(ngModel)]="name" #model="ngModel" appSomeAsyncValidator />
@if (model.pending) {
<app-spinner />
} Реализация пользовательского асинхронного валидатора
В следующем примере асинхронный валидатор проверяет, что актёра назначают на ещё не занятую роль. Новые актёры постоянно проходят прослушивания, а прежние уходят на пенсию, поэтому заранее получить список доступных ролей невозможно. Для проверки потенциальной роли валидатор должен запустить асинхронную операцию и обратиться к централизованной базе данных всех актёров, уже получивших роли.
Следующий код создаёт класс валидатора UniqueRoleValidator, который реализует интерфейс AsyncValidator.
@Injectable({providedIn: 'root'})
export class UniqueRoleValidator implements AsyncValidator {
private readonly actorsService = inject(ActorsService);
validate(control: AbstractControl): Observable<ValidationErrors | null> {
return this.actorsService.isRoleTaken(control.value).pipe(
map((isTaken) => (isTaken ? {uniqueRole: true} : null)),
catchError(() => of(null)),
);
}
}
Свойство actorsService инициализируется экземпляром токена ActorsService, который определяет следующий интерфейс.
interface ActorsService {
isRoleTaken: (role: string) => Observable<boolean>;
}
В реальном приложении ActorsService будет отвечать за отправку HTTP-запроса к базе данных актёров, чтобы проверить, доступна ли роль. С точки зрения валидатора фактическая реализация сервиса не имеет значения, поэтому в примере можно использовать интерфейс ActorsService.
При запуске проверки UniqueRoleValidator передаёт текущее значение элемента управления методу isRoleTaken() класса ActorsService. На этом этапе элемент управления помечается как pending и остаётся в этом состоянии, пока не завершится цепочка Observable, возвращённая методом validate().
Метод isRoleTaken() отправляет HTTP-запрос, проверяющий доступность роли, и возвращает Observable<boolean> в качестве результата. Метод validate() передаёт ответ оператору map и преобразует его в результат проверки.
Как и любой валидатор, метод возвращает null, если форма действительна, и ValidationErrors, если нет. Этот валидатор обрабатывает возможные ошибки оператором catchError. В данном случае валидатор считает ошибку isRoleTaken() успешным результатом проверки, поскольку невозможность выполнить запрос проверки не обязательно означает, что роль недействительна. Можно обработать ошибку иначе и вернуть объект ValidationError.
Через некоторое время цепочка Observable завершается, и асинхронная проверка заканчивается. Флаг pending устанавливается в false, а состояние валидности формы обновляется.
Добавление асинхронных валидаторов в реактивные формы
Чтобы использовать асинхронный валидатор в реактивной форме, сначала внедрите его в свойство класса компонента.
roleValidator = inject(UniqueRoleValidator);
Затем передайте функцию-валидатор непосредственно в FormControl, чтобы применить её.
В следующем примере функция validate из UniqueRoleValidator применяется к roleControl: она передаётся в параметр asyncValidators элемента управления и привязывается к экземпляру UniqueRoleValidator, внедрённому в ActorFormReactiveComponent. Значением asyncValidators может быть одна функция асинхронного валидатора или массив функций. Дополнительные сведения о параметрах FormControl см. в справочнике API AbstractControlOptions.
const roleControl = new FormControl('', {
asyncValidators: [this.roleValidator.validate.bind(this.roleValidator)],
updateOn: 'blur',
}); Добавление асинхронных валидаторов в формы, управляемые шаблонами
Чтобы использовать асинхронный валидатор в форме, управляемой шаблоном, создайте директиву и зарегистрируйте в ней провайдер NG_ASYNC_VALIDATORS.
В приведённом ниже примере директива внедряет класс UniqueRoleValidator, содержащий логику проверки, и вызывает его в функции validate, которую Angular запускает при необходимости выполнить проверку.
@Directive({
selector: '[appUniqueRole]',
providers: [
{
provide: NG_ASYNC_VALIDATORS,
useExisting: forwardRef(() => UniqueRoleValidatorDirective),
multi: true,
},
],
})
export class UniqueRoleValidatorDirective implements AsyncValidator {
private readonly validator = inject(UniqueRoleValidator);
validate(control: AbstractControl): Observable<ValidationErrors | null> {
return this.validator.validate(control);
}
}
Затем, как и для синхронных валидаторов, добавьте селектор директивы к элементу ввода, чтобы активировать её.
<input
type="text"
id="role"
name="role"
#role="ngModel"
[(ngModel)]="actor.role"
[ngModelOptions]="{updateOn: 'blur'}"
appUniqueRole
/> Оптимизация производительности асинхронных валидаторов
По умолчанию все валидаторы запускаются после каждого изменения значения формы. Синхронные валидаторы обычно не оказывают заметного влияния на производительность приложения. Однако асинхронные валидаторы часто выполняют HTTP-запросы для проверки элемента управления. Отправка HTTP-запроса после каждого нажатия клавиши может перегрузить серверный API, поэтому по возможности этого следует избегать.
Можно отложить обновление состояния валидности формы, изменив свойство updateOn с change (значение по умолчанию) на submit или blur.
Для форм, управляемых шаблонами, задайте это свойство в шаблоне.
<input [(ngModel)]="name" [ngModelOptions]="{updateOn: 'blur'}" />
Для реактивных форм задайте это свойство в экземпляре FormControl.
new FormControl('', {updateOn: 'blur'}); Динамическое управление валидаторами в реактивных формах
В сложных реактивных формах может потребоваться добавлять, удалять или изменять валидаторы в зависимости от пользовательского ввода или состояния приложения. Angular предоставляет несколько методов класса AbstractControl для управления валидаторами во время выполнения без повторного создания элементов управления формы.
Добавление и удаление валидаторов
Методы addValidators и removeValidators позволяют изменять валидаторы элемента управления после его инициализации.
onCountryChange(country: string) {
const postalCodeControl = this.profileForm.get('postalCode');
if (country === 'US') {
// Add validators for US postal codes
postalCodeControl.addValidators([Validators.required, Validators.pattern(/^\d{5}$/)]);
} else {
// Remove validators when not US
postalCodeControl.removeValidators([Validators.required]);
}
postalCodeControl.updateValueAndValidity();
} Замена всех валидаторов
Используйте setValidators, чтобы заменить все существующие синхронные валидаторы элемента управления, или clearValidators, чтобы удалить все валидаторы.
toggleStrictNameValidation(isStrict: boolean) {
const nameControl = this.profileForm.get('name');
if (enable) {
// Set strict validation rules
nameControl.setValidators([
Validators.required,
Validators.minLength(3),
Validators.pattern(/^[a-zA-Z]+$/),
]);
} else {
// Clear all validators
nameControl.clearValidators();
}
nameControl.updateValueAndValidity();
}
Тот же подход применяется к асинхронным валидаторам с помощью addAsyncValidators, removeAsyncValidators, setAsyncValidators и clearAsyncValidators.
Запуск обновления проверки
После изменения валидаторов вызовите updateValueAndValidity, чтобы пересчитать состояние проверки элемента управления. Этот метод принимает параметры, определяющие поведение обновления.
// Update control and notify parent
control.updateValueAndValidity();
// Update control only, don't notify parent or emit events
control.updateValueAndValidity({onlySelf: true, emitEvent: false}); Взаимодействие со встроенной проверкой форм HTML
По умолчанию Angular отключает встроенную проверку форм HTML, добавляя атрибут novalidate к внешнему элементу <form>, и использует директивы, чтобы сопоставлять эти атрибуты с функциями-валидаторами в платформе. Если вы хотите использовать встроенную проверку вместе с проверкой Angular, её можно снова включить с помощью директивы ngNativeValidate. Подробности см. в документации API.
Super-powered by Google ©2010–2025.
Code licensed under an MIT-style License. Documentation licensed under CC BY 4.0.
https://angular.dev/guide/forms/form-validation