Асинхронная валидация применяется в тех случаях, когда корректность значения невозможно определить мгновенно. Типичные примеры — проверка уникальности логина или email на сервере, валидация промокодов, запросы к внешним API, сложные бизнес-правила, зависящие от удалённых данных. В Angular асинхронная валидация встроена в систему реактивных и шаблонных форм и реализуется через RxJS.
Асинхронные валидаторы всегда возвращают результат не сразу, а в виде
потока (Observable) или промиса (Promise), что
позволяет форме корректно работать в условиях сетевых задержек и отмены
запросов.
Синхронная валидация:
ValidationErrors | null;Асинхронная валидация:
Observable<ValidationErrors | null>
или Promise<ValidationErrors | null>;Angular обрабатывает оба типа валидаторов параллельно, но статус формы зависит от завершения асинхронных проверок.
При использовании асинхронных валидаторов появляются дополнительные состояния:
PENDING — асинхронная проверка выполняется;VALID — ошибок нет;INVALID — обнаружены ошибки.Статус PENDING особенно важен для пользовательского
интерфейса: он позволяет отображать индикаторы загрузки или временно
блокировать отправку формы.
Асинхронный валидатор — это функция, принимающая
AbstractControl и возвращающая Observable или
Promise.
Пример простого асинхронного валидатора:
import { AbstractControl, ValidationErrors } from '@angular/forms';
import { Observable, of } from 'rxjs';
import { delay, map } from 'rxjs/operators';
export function forbiddenNameAsync(): (control: AbstractControl) => Observable<ValidationErrors | null> {
return (control: AbstractControl): Observable<ValidationErrors | null> => {
return of(control.value).pipe(
delay(1000),
map(value => value === 'admin' ? { forbiddenName: true } : null)
);
};
}
Ключевые особенности:
null означает отсутствие ошибок.В реактивных формах асинхронные валидаторы передаются третьим
аргументом в FormControl.
this.form = new FormGroup({
username: new FormControl(
'',
[Validators.required],
[forbiddenNameAsync()]
)
});
Порядок аргументов строго фиксирован:
На практике асинхронная валидация почти всегда связана с HTTP-запросами. Валидатор обычно использует сервис.
@Injectable({ providedIn: 'root' })
export class UserService {
constructor(private http: HttpClient) {}
checkUsername(username: string): Observable<boolean> {
return this.http.get<boolean>(`/api/users/check/${username}`);
}
}
Асинхронный валидатор:
export function usernameExistsValidator(userService: UserService) {
return (control: AbstractControl): Observable<ValidationErrors | null> => {
if (!control.value) {
return of(null);
}
return userService.checkUsername(control.value).pipe(
map(exists => exists ? { usernameExists: true } : null)
);
};
}
Использование:
this.form = new FormGroup({
username: new FormControl(
'',
[Validators.required],
[usernameExistsValidator(this.userService)]
)
});
Angular автоматически отменяет предыдущие асинхронные проверки, если значение контрола изменилось. Это достигается благодаря механизму подписок RxJS и предотвращает устаревшие результаты.
Для снижения нагрузки рекомендуется:
debounceTime;Пример с оптимизацией:
return control.valueChanges.pipe(
debounceTime(500),
switchMap(value => userService.checkUsername(value)),
map(exists => exists ? { usernameExists: true } : null),
first()
);
В шаблонных формах асинхронные валидаторы реализуются через директивы.
@Directive({
selector: '[usernameExists]',
providers: [
{
provide: NG_ASYNC_VALIDATORS,
useExisting: UsernameExistsDirective,
multi: true
}
]
})
export class UsernameExistsDirective implements AsyncValidator {
constructor(private userService: UserService) {}
validate(control: AbstractControl): Observable<ValidationErrors | null> {
return this.userService.checkUsername(control.value).pipe(
map(exists => exists ? { usernameExists: true } : null)
);
}
}
Использование в шаблоне:
<input name="username" ngModel usernameExists />
Асинхронные ошибки отображаются так же, как синхронные. Они попадают
в объект errors контрола.
<div *ngIf="form.get('username')?.pending">
Проверка...
</div>
<div *ngIf="form.get('username')?.errors?.['usernameExists']">
Имя пользователя уже занято
</div>
Важно учитывать, что ошибки появляются только после завершения
проверки, а во время выполнения контрол находится в состоянии
PENDING.
Angular поддерживает одновременное использование нескольких асинхронных валидаторов. Все они выполняются параллельно, а результат объединяется.
new FormControl(
'',
[],
[
usernameExistsValidator(this.userService),
anotherAsyncValidator()
]
);
Контрол считается валидным только в том случае, если все асинхронные
валидаторы вернули null.
Форма не переходит в состояние VALID, пока хотя бы один
контрол находится в PENDING. Это предотвращает
преждевременную отправку данных.
Проверка перед отправкой:
if (this.form.valid) {
// отправка данных
}
При наличии активной асинхронной валидации valid будет
равно false.
Observable или
Promise;Асинхронная валидация должна быть чистой, детерминированной и не изменять состояние приложения напрямую.