ONE DUDE`S BLOG

/media/top-18-most-common-angularjs-developer-mistakes-41f9ad303a51db70e4a5204e101e7414.webp

Альтернативная валидация реактивных форм Angular2+

20.12.2020
Валидация Angular2+ реактивных форм с помощью директив.

Проблема ;(

Зачастую, в проектах что мы ведем, используются десятки, сотни, а иногда даже тысячи форм (если вам посчастливилось разрабатывать свою crm). В каждой из форм мы используем одни и теже механизмы валидации, по событию submit проверяем валидна ли форма, и если нет - помечаем ее как посещеную (устанвливая фокус/изменение с помощью свойств dirty и touched).

Проблема данного подхода слабо ощутима в маленьких проектах, но куда большее влияние она оказывает на действительно громоздкие системы. Мало того что мы постоянно дублируем один и тот же код

     someFormGroup.markAllAsTouched() //
     // Либо
     someFormGroup.controls.forEach(c => {
       c.markAsDirty()
     })

Так ко всему прочему, данный подход, зачастую, размывает ответственность метода onSubmit, что не есть хорошо. Кроме того, при изменениях механизма валидации/добавлении новой логики, мы будем вынуждены пройтись по всем формам в проекте, меняя функционал.

Как пример из жизни: в 1 момент, в достаточно крупном проекте, заказчик решил добавить функцию справки для различных полей в специальном попапе, который показывается в случае ошибки. Если реализовывать данный функционал в лоб мы будем вынуждены менять каждый компонент, где используются формы.

Решение ;)

Решить данную проблему позволит сквозное программирование с использованием angular директив. Директива позволит нам получить доступ к formGroup, а также перехватить событие submit с помощью HostListener/addEvenListener/fromEvent(rxjs). Используя данный подход мы получим 1 entrypoint для всех механизмов программной валидации, что позволит легко интегрировать новую логику, а также изменить существующую.

Реализация

Реализация крайне тривиальна, нам достаточно привязать директиву к селектору реактивных форм, получить экземпляр FormGroup и перехватить событие submit:

import { Directive, HostListener, Input } from "@angular/core";
import { FormGroup } from "@angular/forms";
import { MatSnackBar } from "@angular/material/snack-bar";

@Directive({
  selector: "form[formGroup]"
})
export class FormValidationDirective {
  @Input() formGroup: FormGroup;

  @HostListener("submit", ["$event"])
  formSubmit(): void {
    if (this.formGroup.valid) {
      return;
    }
    this.formGroup.markAllAsTouched();
    // Как пример расширения используем popup с простым сообщением для пользователя

    this.showErrorPopup();
  }

  private showErrorPopup(): void {
    this.snackBar.open("User be careful", "Warning", { duration: 3000 });
  }

  constructor(private snackBar: MatSnackBar) {}
}

Далее нам останется лишь задекларировать нашу директиву (либо использовать как внешний пакет)

@NgModule({
  declarations: [
    FormValidationDirective,
    ...
  ],
  ...
})

Пример можно пощупать https://stackblitz.com/github/Artawower/angular-form-validation

Angular 2+
3
1410