Реактивные формы (Reactive Forms) в Angular 2+
В этой статье мы рассмотрим использование и базовое API реактивных форм в Angular на примере создания простой формы регистрации. Демо и исходный код того, что получилось в итоге.
Использование реактивных форм предполагает создание модели формы в классе компонента, связывание (two way data binding) этой модели с DOM элементами в шаблоне компонента используя ряд доступных директив.
Достоинства модель-ориентированных форм :
- Вся логика работы с формой содержится в компоненте а не в шаблоне, что более предпочтительно при сложных формах
- Классы
FormGroup
иFormControl
имеют хорошее API, которое позволяет строить пользовательский интерфейс используя реактивный стиль программирования - Обновление данных всегда являются синхронными и находятся под вашим контролем
- Возможность юнит тестирования.
Реактивное программирование в Angular 2
Главная идея состоит в том, что элементы форм и сами формы обеспечивают Observable-based API
. Вы можете думать о источниках данных (observables) как о коллекции значений изменяющихся в течение времени.
Т.е. реактивные формы и их отдельные элементы могут рассматриваться как постоянный источник данных на изменение которых можно подписаться и обрабатывать используя определенный набор методов.
Form API
Реактивные формы в Angular представлены тремя разными элементами формы.
FormControl
— элемент ввода, может быть связан с input, textarea, select и прочими html элементами форм.FormGroup
— состоит изFormControl
иFormGroup/FormArray
, каждый элемент содержит строковый идентификатор. Может быть целой формой или же частью формы (вложенной формой).FormArray
— похож наFormGroup
, но действует как массив (вы можете использовать методыpush
,insert
, илиremoveAt
), используется для форм с динамическим набором элементов (например: список IP адресов, список контактных данных);
Каждый этот элемент наследует от AbstractControl
. Если мы посмотрим на этот абстрактный класс, то увидим что все они получили следующие свойства.
FormControl и FormGroup
FormControl
— это класс, который позволяет напрямую создавать отдельные элементы ввода и управлять ими. Конструктор new FormControl()
принимает три необязательных параметра:
- изначальное значение данных
- массив валидаторов
- массив асинхронных валидаторов
Пример создания простого элемента ввода:
FormGroup
объединяет в себеFromContro\FormArray
. По сути, Ваша форма это всегда FormGroup
. Например:
Другая приятная вещь это то, что FormGroup
могут быть вложенными, а так же иметь собственный валидатор для всей группы. Например, поле address
- это вложенный набор элементов ввода.
Связывание форм шаблона с помощью formGroup, formGroupName и formControlName
В данный момент в нашем коде нет ничего что говорит о том, что модель формы имеет отношение к форме в шаблоне компонента. Нам нужно связать модель с нашей формой и мы можем это сделать, используя директиву formGroup
, которая принимает ссылку на экземпляр FormGroup
.
Чтобы использовать эту директиву нам нужно импортировать модуль ReactiveFormsModule
в модуль нашего приложения.
Теперь мы можем пойти дальше и использовать formGroup
для связывания модели с шаблоном формы:
Следующее, что нам нужно сделать, это связать отдельные элементы формы с полями ввода в шаблоне. Для этого мы будем использовать директиву formControlName
, которая в качестве значения принимает название (ключ) элемента ввода из модели формы.
Так как адрес у нас тоже является форм-группой, мы можем связать группу элементов управления формой с DOM элементами, используя formGroupName
. Для этого нам нужен окружающий тег, иначе мы не сможем использовать эту директиву. Поэтому, обернем поля ввода в тег fieldset
, к которому и применим эту директиву.
Теперь модель FormGroup
соответствуют структуре DOM элементов.
FormArray и formArrayName
В нашем случае мы хотим, чтобы пользователь имел возможность добавлять неограниченное количество контактных данных. Именно для этих целей отлично подходит FormArray
. Итак, объявим поле contacts
как FormArray
:
Чтобы иметь возможность динамически добавлять новые контакты мы в классе компонента определим метод addContact
:
В коде приведенном выше мы через метод формы get
получаем доступ к полю contacts
и далее, как в обычный массив, добавляем в него новый объект FormGroup
, который состоит из двух полей type
и value
.
Так же нам нужно дать пользователю возможность удалять контакты из массива. Для этого определим метод removeContact
:
Здесь, как и выше, обращаемся к полю contacts
и удаляем контакт, используя метод removeAt
объекта FormArray
, он принимает в качестве параметра индекс элемента, который нужно удалить.
Связывание FormArray
с шаблоном формы:
Для этого я воспользовался директивой formArrayName
, которая ссылается на наш объект FormArray
, затем проитерировал все элементы массива с помощью директивы ngFor
и связал вложенные элементы формы используя директиву [formControl]
.
Валидация реактивных форм
Теперь, когда модель формы связана с шаблоном, мы можем приступить к добавлению валидаторов для элементов управления формой. Чтобы использовать стандартные валидаторы нам нужно импортировать класс Validators
из @angular/forms
в нашем компоненте и передать их в качестве второго параметра конструкторам FormControl
. Если нужно указать несколько валидаторов - они заключаются в массив. Пример:
К сожалению, в документации не отображен список всех стандартных валидаторов, но мы можем посмотреть его в исходном коде.
Добавление кастомных валидаторов
Кроме использования стандартных валидаторов, мы так же можем определять свои валидаторы. Например, создадим в классе компонента валидатор для поля user_name
.
Валидатор для поля contacts
:
Так же для валидации телефона:
И для проверки на совпадение паролей:
Теперь мы можем добавить их к нашей модели формы:
Вывод сообщений об ошибках валидации
Для того, чтобы показывать пользователю сообщения о том, что он ввел не корректные данные мы создадим простой компонент в который будем передавать элемент управления формой FormControl
и который будет показывать ошибки для этого элемента.
В этом компоненте мы обращаемся к свойствам invalid
и touched
, чтобы узнать что пользователь уже ввел значение и являются ли они не корректными. А так же к полю errors
объекта FormControl
для того, чтобы узнать какие именно ошибки содержит поле. Теперь мы можем использовать validator-message
компонент в шаблоне формы, например:
Динамичная валидация
Как уже упоминалось выше, в нашем случае каждый контакт — это форм-группа из двух элементов: type
и value
Элемент value
должен валидироваться по разному в зависимости от выбранного type
(e-mail, phone, skype).
В коде приведенном выше мы дописали в метод добавления нового контакта следующее:
- подписались на изменение поля
type
в только что добавленной форм-группе с помощьюvalueChanges.subscribe
; - установили новые валидоры используя метод
setValidators
; - вызвали метод
updateValueAndValidity
который пересчитывает значение и статус элемента;
Упрощение с помощью FormBuilder
В данный момент создание модели формы выглядит следующим образом:
Однако, данный код выглядит достаточно многословным, из-за многократного вызова new FormControl()
и new FormGroup()
. Но мы можем использовать API более высокого уровня, чтобы сделать определение формы немного лаконичнее.
FormBuilder
— это синтаксический сахар, который создает экземпляры FormGrouop
, FormControl
, и FormArray
вместо нас.
Теперь создание нашей формы с помощью FormBuilder
выглядит более кратко:
На этом все, надеюсь этот материал был полезен для Вас. Я не претендую на звание гуру, поэтому если у Вас есть замечания / дополнения буду рад их увидеть в комментариях. А в следующей статье я рассмотрю создание кастомных компонентов элементов форм.