В прошлых темах был описан подход Template-Driven, который концентрировался вокруг шаблона компонента: для работы с формой и ее элементами в шаблоне компонента к элементам html применялись директивы NgModel и NgForm, правила валидации задавались в тегах элементов с помощью атрибутов required и pattern. Но есть альтернативный подход — использование реактивных форм (Reactive Forms). Рассмотрим, в чем он заключается.
При подходе Reactive Forms для формы создается набор объектов FormGroup и FormControl. Сама форма и ее подсекции представляют класс FormGroup, а отдельные элементы ввода — класс FormControl. Например, базовое создание формы:
Здесь определено два элемента: userName и userEmail.
Объект FormControl может иметь различные формы определения. В частности, в качестве первого параметра можно передавать значение по умолчанию для элемента, а в качестве второго параметра — набор валидаторов:
Здесь к элементу userEmail применяется валидатор Validators.required, который требует обязательного наличия значения. Если валидаторов несколько, то они заключаются в массив. Все встроенные валидаторы можно посмотреть в документации.
Для привязки объекта myForm к конкретному элементу формы применяется атрибут formGroup:
1
<form[formGroup]="myForm"></form>
Кроме того, необходимо связать объекты FormControl с элементами ввода с помощью атрибута formControlName:
1
<inputname="name"formControlName="userName"/>
Данный элемент будет связан с объектом "userName": new FormControl("Tom").
Теперь рассмотрим, как эти объекты будут взаимодействовать с шаблоном компонента. Для этого определим следующий компонент:
С помощью выражений myForm.controls['userName'] мы можем обратиться к нужному элементу формы и получить его состояние или значение. В данном случае если значение поля ввода невалидно, и при этом поле ввода уже получало фокус, то отображается ошибка валидации.
Но чтобы все это заработало, необходимо импортировать модуль ReactiveFormsModule. Для этого изменим модуль приложения AppModule:
По сути валидатор представляет обычный метод — в данном случае метод userNameValidator. В качестве параметра он принимает элемент формы, к которому этот валидатор применяется, а на выходе возвращает объект, где ключ — строка, а значение равно true.
В данном случае проверяем, если значение равно строке "нет", то возвращаем объект {"userName": true}. Значение true указывает, что элемент формы не прошел валидацию. Если же все нормально, то возвращаем null.
Затем этот валидатор добавляется к элементу:
1
"userName": new FormControl("Tom", [Validators.required, this.userNameValidator])
И в случае если в поле для ввода имени будет введено значение "нет", то данное поле не пройдет валидацию:
Некоторые элементы на форме могут относиться к одному и тому же признаку. Например, в анкете пользователя могут попросить указать номера телефоно, которыми он владеет. Их может быть несколько, но они будут представлять один и тот же признак — "номера телефонов". То есть логично было бы объединить все поля для ввода номеров телефонов в массив. И в Angular 2 мы легко можем реализовать подобную возможность с помощью класса FormArray.
Итак, изменим код компонента AppComponent следующим образом:
В этой сложной конструкции мы сначала получаем объект формы через выражение this.myForm.controls["phones"], затем приводим его к типу FormArray. И далее как и в обычный массив добавляем через метод push новый элемент.
В коде html предусматриваем вывод объектов на форму с помощью директивы ngFor:
1 2 3 4 5 6 7 8 9101112
<divformArrayName="phones"><divclass="form-group"*ngFor="let phone of myForm.controls['phones'].controls; let i = index"><label>Телефон</label><inputclass="form-control"formControlName="{{i}}"/></div></div>
При этом контейнер всех элементов ввода имеет директиву formArrayName="phones". А каждый элемент в качестве названия принимает его текущий индекс: formControlName="{{i}}".
А кнопка "Добавить телефон" позволяет добавить на форму новое поле для ввода еще одного телефонного номера:
FormBuilder передается в качестве сервиса в конструктор. С помощью метода group() создается объект FormGroup. Каждый элемент передается в форму в виде обычного массива значений:
1
"userName": ["Tom", [Validators.required]]
Результат работы компонента будет аналогичным предыдущему.