Альтернативная валидация реактивных форм 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