Формы и валидация данных в Angular

В отличии от AngularJS в Angular сделали целый модуль, который помогает в обработке форм.

Angular предлагает 2 подхода работы с формами:

  • основанный на шаблонах (template-driven)
  • реактивный (reactive)

Шаблонный более традиционный для декларативного подхода фреймворка. Реактивный появился позже и считается более прогрессивным и гибким. Мы разберем оба подхода.

Подключение модуля форм

Для начала работы с формами необходимо подключить модуль форм(FormsModule):

import { FormsModule } from '@angular/forms';

если мы хотим работать в реактивном подходе, то подключаем модуль ReactiveFormsModule:

import { ReactiveFormsModule } from '@angular/forms';

И конечно импортируем их в наш основной модуль:

Шаблоно-ориентированный подход (template-driven)

В шаблонно ориентированном подходе мы по большей части оперируем 3мя сущностями: ngModel, ngModelGroup и ngForm.

ngModel

Чтобы привязать данные к элементу формы мы можем поставить директиву ngModel:

<input type="text" [ngModel]="name">

Есть 3 способа как получить введенные данные:

  • двухстороннее связывание
  • обработку события изменения
  • ссылку на элемент (либо ngModel)

ngModel и двухстороннее связывание (two way binding)

Способ, который пришел из AngularJs и в Angular практически не используется, но тем не менее, его хорошо знать:

<input type="text" [(ngModel)]="name">

то есть вы используете специальный синтаксис двойных скобок: сначала прямоугольные, затем круглые ( чтобы было проще запомнить последовательность используется подсказка “коробка с бананами” )

Теперь при вводе данных в элемент формы у нас будет обновляться и свойство контроллера name.

ngModel и обработка события

Вы можете использовать как родное событие поля ввода, например: для type text это будет input:

<input type="checkbox" [ngModel]="name" (input)="name = $event.target.value">

так и использовать специальное событие ngModelChange:

<input type="text" [ngModel]="name" (ngModelChange)="name = $event">

ngModel и обращение по ссылке

Чтобы поставить ссылку на ngModel, мы делаем следующее:

<input type="text" [ngModel]="name" #name>

теперь мы можем обратиться в шаблоне к значению свойства элемента:

{{name.value}}

либо в контроллере компонента через специальный декоратор ViewChild:

Также мы можем сделать ссылку не только на элемент, но и на сам контроллер ngModel:

<input type="text" [ngModel]="name" #nameModel="ngModel">

Теперь в nameModel нам доступно не только значение модели, но и все дополнительные свойства и методы контроллера модели:

nameModel.valid

Валидация данных в шаблонном подходе

Полностью(или почти полностью) поддерживаются HTML5 валидация. То есть, например, если проставить полю атрибут require, то Angular его подхватит.

Свои валидаторы в шаблонном подходе

Не самый очевидный способ предлагают нам разработчики ангуляра для того, чтобы расширить валидаторы в шаблонном подходе. Мы должны создать директиву, в которой переопределить набор стантартных валидаторов NG_VALIDATORS, точнее дополнить своим(своими) с помощью флага multi:

таким образом мы создали свой валидатор, который проверит не является ли значение равным ‘3’. Плюс попрошу обратить ваше внимание на селектор для директивы мы указали не только сам атрибут имени директивы, но также что на элементе будет присутствовать ngModel.

Свои асинхронные валидаторы в шаблонном подходе

Полностью идентичны синхронным, отличие только в том что ваш метод валидации возвращает Promise либо Observable, а прописывается не в NG_VALIDATORS, в а NG_ASYNC_VALIDATORS:

Вывод ошибок

Чтобы вывести ошибки также можем обратиться к контроллеру ngModel по ссылке и получить его свойство errors:

{{nameModel.errors | json}}

.

…и теперь…

.

Реактивный (reactive) подход

Реактивном подходе мы настраиваем элементы формы не в шаблоне, а в контроллере, оперируя при этом следующими понятиями: FormControl, FormGroup, FormArray, FormBuilder.

На каждый элемент формы создается(вручную или автоматически) свой FormControl.

FormControl

Чтобы создать FormControl мы определяем свойство в контроллере компонента:

name: FormControl = new FormControl('Alice');

и привязываем его к конкретному элементу в шаблоне:

<input type="text" [formControl]="name">

После чего в контроллере мы можем подписаться на изменения:

this.name.valueChanges.subscribe(console.log)

Либо на изменения статуса(валидности):

this.name.statusChanges.subscribe(console.log)
Также можно получать статические значения (не подписываясь):
this.name.value
this.name.valid
 Полный список свойств можно посмотреть в описании AbstractControl (от которого наследуется FromControl)

FormGroup

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

и подключаем в шаблоне:

Обратите внимание, что для контролов обернутых в formGroup мы уже ставим formControlName свойство (а не [formControl]), которое будет указывать на то, где брать контрол в группе.

FormArray

FormArray прекрасно подойдет тогда, когда в форме у вас используется список, в который вы можете добавлять элементы. Давайте сделаем форму “учасников”, которая содержит список участников:

и в шаблоне это будет выглядеть так:

как видите мы выводим список контролов используя итератор(index) как имя контрола(formControlName) в списке.

Давайте добавим возможность добавления участников:

и для этого в контроллере компонента определим 2 дополнительных метода removeUser и addUser:

В итоге получим:

FormBuilder

Упростить процесс создания новых форм нам помогает сервис FormBuilder. Который мы можем инжектировать в контроллер:

После чего создать туже форму (“учасники”), только теперь с помощью сервиса:

вы можете сказать, что кода не стало меньше. Да, это так. FormBuilder имеет смысл использовать только для сложных форм с большим динамическим количеством контролов.

Валидация данных в реактивном подходе

Чтобы добавить валидатор на контрол мы всего лишь добавляем второй параметр, который может быть как одним валидатором, так и массивом(применяем разные проверки):

name: FormControl = new FormControl('Alice', [Validators.required]);

предварительно импортировав коллекцию валидаторов:

import { Validators } from “@angular/forms”;

Мы можем задавать один и больше валидатор. На данный момент в коллекции есть следующие валидаторы:

  • min
  • max
  • required
  • requiredTrue
  • email
  • minLength
  • maxLength
  • pattern

Создание своих валидаторов

Валидатор представляет из себя простую функцию, которая возвращает либо объект ошибки либо null:

добавляется вторым параметром в контрол( вместе со стандартными валидаторами):

name: FormControl = new FormControl('Alice', [Validators.required, myNameValidator]);

В реальных проектах валидаторы собирают в группы заворачивают в классы и делают статическими методами классов.

Валидаторы с параметрами

Для создания валидаторов с параметрамми нам всего лишь необходимо завернуть нашу функцию валидатор в еще одну функцию, которая создаст замыкание для хранения параметров:

Асинхронные валидаторы

Асинхронные валидаторы отличаются от синхронных только тем, что возвращают не статические данные, а Observable либо Promise:

и также определяются 3-м параметром в конструкторе контрола:

name: FormControl = new FormControl('Alice', [myNameValidator], [myAsyncNameValidator]);

Вывод ошибок

Ошибки контрола хранятся в свойстве errors:

this.name.errors

Чтобы сделать какие-то действия в случае появления ошибок, можем написать так: добавив фильтры на изменение состояния и собственно валидность данных:

Свойство есть не только у обычного контрола, но так же и у FormGroup, поэтому вы можете получить объект со всеми ошибками формы:

this.participantsForm.errors

Создание кастомных контроллов

Решил вынести эту главу в отдельный пост.