Пользовательские валидаторы

Валидация формы — важная часть любого веб-приложения. В Angular поддерживаются как встроенные валидаторы, так и возможность создания собственных, пользовательских. Пользовательские валидаторы позволяют задать специфические условия для проверки данных, которые не могут быть покрыты стандартными средствами фреймворка.

Структура валидатора

Пользовательский валидатор в Angular представляет собой функцию, которая принимает объект AbstractControl и возвращает либо объект с ошибками, либо null, если данные валидны. Этот валидатор можно использовать как в реактивных, так и в шаблонных формах.

Пример структуры валидатора:

import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';

export function customValidator(control: AbstractControl): ValidationErrors | null {
  // Логика проверки
  if (control.value && control.value.length < 5) {
    return { 'customError': { message: 'Значение слишком короткое' } };
  }
  return null; // Возвращается null, если значение валидно
}
  • AbstractControl — это абстракция, которая предоставляет доступ к значениям и состоянию формы или поля.
  • ValidationErrors — объект ошибок, где ключами являются названия ошибок, а значениями — данные ошибки.

Если валидатор возвращает null, это означает, что валидация прошла успешно. В противном случае возвращается объект с ошибками.

Использование пользовательского валидатора в реактивных формах

Для использования пользовательского валидатора в реактивных формах, его необходимо передать в настройку поля формы с помощью метода setValidators. В отличие от шаблонных форм, где валидаторы добавляются прямо в HTML, в реактивных формах валидаторы добавляются программно.

Пример использования валидатора в реактивной форме:

import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { customValidator } from './custom-validator';

@Component({
  selector: 'app-custom-form',
  templateUrl: './custom-form.component.html'
})
export class CustomFormComponent {
  form: FormGroup;

  constructor(private fb: FormBuilder) {
    this.form = this.fb.group({
      username: ['', [Validators.required, customValidator]]
    });
  }

  onSubmit() {
    if (this.form.valid) {
      console.log('Форма отправлена');
    } else {
      console.log('Ошибка валидации');
    }
  }
}

В данном примере поле username валидируется с помощью стандартного валидатора Validators.required и пользовательского валидатора customValidator.

Использование пользовательского валидатора в шаблонных формах

В шаблонных формах валидаторы применяются через атрибуты в HTML-разметке. Для этого пользовательский валидатор можно подключить через директиву ngModel и задать его в списке валидаторов.

Пример использования валидатора в шаблонной форме:

<form #form="ngForm">
  <input name="username" ngModel [ngModelOptions]="{standalone: true}" [ngModel]="username" required appCustomValidator>
  <div *ngIf="form.controls['username']?.errors?.customError">
    {{ form.controls['username'].errors.customError.message }}
  </div>
</form>

В этом примере валидатор будет связан с элементом через кастомную директиву. К тому же, ошибки выводятся с использованием стандартной директивы ngIf для контроля состояния ошибок.

Обработка ошибок

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

if (this.form.controls['username'].hasError('customError')) {
  console.log(this.form.controls['username'].errors['customError'].message);
}

Этот код проверяет наличие ошибки customError и выводит сообщение об ошибке, если оно существует.

Асинхронные пользовательские валидаторы

В некоторых случаях необходимо проверять данные асинхронно, например, при запросах к серверу для проверки уникальности значения. В Angular для этого предусмотрены асинхронные валидаторы, которые возвращают Observable вместо обычного объекта ошибки или null.

Пример асинхронного валидатора:

import { AbstractControl, ValidationErrors, AsyncValidatorFn } from '@angular/forms';
import { Observable, of } from 'rxjs';
import { debounceTime, switchMap, map, catchError } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';

export function uniqueUsernameValidator(http: HttpClient): AsyncValidatorFn {
  return (control: AbstractControl): Observable<ValidationErrors | null> => {
    return of(control.value).pipe(
      debounceTime(300),
      switchMap(value => 
        http.get(`https://api.example.com/check-username?username=${value}`).pipe(
          map((response: any) => (response.isAvailable ? null : { 'usernameTaken': true })),
          catchError(() => of(null))
        )
      )
    );
  };
}

В данном примере проверяется, доступен ли выбранный пользователем логин через запрос к серверу. Если логин занят, возвращается ошибка с ключом usernameTaken.

Асинхронные валидаторы применяются в форме так же, как и обычные:

this.form = this.fb.group({
  username: ['', [Validators.required], [uniqueUsernameValidator(this.http)]]
});

Обработка нескольких валидаторов

Angular позволяет комбинировать несколько валидаторов — как синхронных, так и асинхронных. В случае, если несколько валидаторов возвращают ошибку, все ошибки сохраняются в объекте ошибок, и все сообщения ошибок могут быть отображены в интерфейсе.

this.form = this.fb.group({
  email: [
    '',
    [Validators.required, Validators.email],
    [this.emailAsyncValidator]
  ]
});

Здесь поле email проверяется как на обязательность заполнения, так и на корректность email-формата с помощью встроенного валидатора Validators.email. Параллельно применяется асинхронная проверка на уникальность email через emailAsyncValidator.

Важные моменты

  • Зависимости: Если валидатор зависит от внешних ресурсов (например, от HTTP-запроса), его стоит реализовывать как асинхронный.
  • Отображение ошибок: Важно корректно обрабатывать и показывать ошибки, чтобы пользователь мог понять, какие данные необходимо исправить.
  • Перезапуск валидатора: Если после изменений необходимо перезапустить валидатор, можно вызвать updateValueAndValidity() на поле формы.

Использование пользовательских валидаторов позволяет гибко настроить валидацию форм, обеспечивая их соответствие бизнес-логике и требованиям проекта.