Введение
Одним из достоинств Angular является широкий набор инструментов “из коробки”, которые позволяют быстро создавать формы любой сложности.
В Angular существует 2 подхода к созданию форм:
Template-driven forms — подход, в котором ключевую роль играет шаблон компонента, и все описание производится в нем — этот подход является развитием работы с формами в AngularJS;
Reactive forms — новый подход для работы с формами в реактивном стиле. Описание формы происходит в компоненте в виде дерева объектов, после чего это дерево связывается с шаблоном. Все манипуляции (проверка валидности, подписка на изменение значения и прочее) производятся в компоненте, что делает работу более гибкой, удобной и предсказуемой.
В данной статье мы разберем, как начать работать с reactive forms на примере простой формы с валидацией и сообщениями об ошибках. Код примера.
Создание реактивной формы
Подключим ReactiveFormsModule в модуль, в котором будем использовать форму:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
@NgModule({
imports: [ BrowserModule, ReactiveFormsModule ],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
В реактивных формах используются 3 типа блоков:
- FormControl — одиночный контрол формы;
- FormGroup — группа контролов формы;
- FormArray — массив контролов формы.
Все они наследуются от Abstract Control.
Описывать форму удобно, используя специальный инструмент FormBuilder, с помощью которого можно создавать перечисленные выше блоки.
Добавим в компонент формы FormBuilder и FormGroup:
import { Component } from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
myFirstReactiveForm: FormGroup;
constructor(private fb: FormBuilder){}
ngOnInit(){}
}
Теперь опишем форму и инициализируем ее в ngOnInit:
export class AppComponent implements OnInit {
myFirstReactiveForm: FormGroup;
constructor(private fb: FormBuilder){}
ngOnInit(){
this.initForm();
}
/** Инициализация формы*/
initForm(){
this.myFirstReactiveForm = this.fb.group({
name: ['Иван'],
email: [null]
});
}
}
Данная форма состоит из двух контролов:
- name со значением «Иван» при инициализации;
- email без стартового значения.
Свяжем форму с шаблоном компонента через директивы formGroup и formControlName:
<form [formGroup]="myFirstReactiveForm">
<label for="name">имя</label>
<input type="text" id="name" formControlName="name" />
<br/><br/>
<label for="email">email</label>
<input type="text" id="email" formControlName="email" />
<br/><br/>
<button type="submit">отправить</button>
</form>
Тут нужно обратить внимание на то, что formControlName принимает имя строкой и пишется без [ ].
Данные формы мы можем получить в компоненте в виде объекта через свойство value и вывести их в шаблон через jsonPipe (на данном этапе это необходимо для проверки работоспособности):
<div>
{{myFirstReactiveForm.value | json}}
</div>
Валидация и подсветка не валидных контролов
Angular предоставляет возможность валидации с помощью списка статических методов класса Validators, проверяющих соответствие инпута определенным условиям.
Мы используем следующие:
- Validators.required — делает контрол обязательным для заполнения;
- Validators.email — валидация эл. адреса;
- Validators.pattern — валидация по регулярному выражению.
Импортируем валидаторы из angular/forms в компонент:
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
Добавим их в описание контролов формы:
this.myFirstReactiveForm = this.fb.group({
name: ['', [
Validators.required,
Validators.pattern(/[А-я]/)
]
],
email: ['', [
Validators.required, Validators.email
]
]
});
На все контролы формы Angular динамически добавляет парные CSS классы в зависимости от определенных условий:
- ng-invalid/ng-valid — меняется в зависимости от валидности контрола;
- ng-pristine/ng-dirty — контрол считается dirty, если в нем хотя бы раз менялось значение;
- ng-untouched/ng-touched — контрол считается touched при первой потере фокуса.
В CSS добавим следующие стили:
input.ng-touched.ng-invalid{
border-color: red;
}
Теперь при введении неверных данных и потере фокуса контролы будут иметь красный бордер.
Вывод сообщения об ошибке
Получить доступ к контролу в компоненте можно следующим образом:
this.myFirstReactiveForm.controls[controlName]
Проверим свойства invalid и touched.
Полный список свойств и методов контрола смотреть здесь.
Добавим в компонент метод для проверки валидности контрола, который принимает на вход имя контрола и возвращает true/false:
isControlInvalid(controlName: string): boolean {
const control = this.myFirstReactiveForm.controls[controlName];
const result = control.invalid && control.touched;
return result;
}
В шаблоне добавим под контролом div с сообщением об ошибке, который будет отображаться по *ngIf, если контрол не валидный:
<label for="name">имя</label>
<input type="text" id="name" formControlName="name" />
<div class="error" *ngIf="isControlInvalid('name')">
Имя должно состоять только из русских букв
</div>
Для вывода разных ошибок (в зависимости от условий) можно воспользоваться библиотекой ngx-errors.
Отправка формы
Добавим в компонент метод onSubmit:
onSubmit() {
const controls = this.myFirstReactiveForm.controls;
/** Проверяем форму на валидность */
if (this.myFirstReactiveForm.invalid) {
/** Если форма не валидна, то помечаем все контролы как touched*/
Object.keys(controls)
.forEach(controlName => controls[controlName].markAsTouched());
/** Прерываем выполнение метода*/
return;
}
/** TODO: Обработка данных формы */
console.log(this.myFirstReactiveForm.value);
}
Если форма не валидна, через foreach помечаем все контролы как touched для подсветки ошибок и прерываем выполнение метода. В противном случае обрабатываем данные формы.
Добавим обработчик события submit в шаблон:
<form [formGroup]="myFirstReactiveForm" (submit)="onSubmit()">
Форма готова!
Заключение
В следующей части разберем реактивную работу с формами, а именно:
подписку на событие изменения контрола;
динамический сброс и блокировку зависимых контролов;
динамическое добавление и удаление контролов и групп контролов в форму.
Ссылки
Код примера смотреть здесь.
Более подробную информацию можно получить из официальной документации.
Все интересующиеся Angular могут присоединяться к группе русскоговорящего Angular сообщества в Telegram.
Комментарии (13)
dreamseeker92
09.01.2018 07:14автор забыл упомянуть, что можно создавать группы внутри групп. Это важно для компонетизации на мой взгляд. Так же для новичков лучше засветить ControlValueAccessor. В целом статья информативная
klimentRu Автор
09.01.2018 07:17Про вложенные группы и другие более сложные кейсы напишу во второй части. Тут хотелось на простом примере показать как начать работать с реактивными формами.
MOTORIST
09.01.2018 17:01Не обязательно гонять все поля формы на ошибки, можно просто заблокировать кнопку отправки:
<button type=«submit» [disabled]="!reactiveForm.valid">klimentRu Автор
09.01.2018 18:01+1Согласен, но в моей практике был кейс, когда надо было делать именно подсветку не валидных контролов.
Подумал, что это более интересный вариант.
shuron
В чем таки достоинства Reactive forms в сравнении с Template-driven?
Template-driven вариант мне кажется более интуитивным и простым. Может быть это связано с тем что я не видел каких-то особых ситуаций ( Я во фронтэнде только набегами, раз в пятилетку, на ангуляре первый серьезный проект)? Мог-бы кто-то пролить свет на это?
klimentRu Автор
В официальной документации написано, что это просто 2 разных подхода со своими плюсами и минусами. Можно прочитать ne.
При работе с большими и сложными формами реактивный подход удобнее по следующим причинам:
Vadem
Разве в случае Template-driven подхода обязательно создавать для каждого контрола свойство?
Там же тоже можно получить доступ к свойству value у FormGroup.
Например, если у вас в шаблоне есть форма:
То в компоненте можно писать так:
klimentRu Автор
По моему получение формы через ViewChild менее удобный и точно менее понятный способ, чем описание в компоненте всй формы с валидациями. Особенно если форма большая.
Из официальной документации:
Так что не стоит холиварить. В некоторых ситуациях использование template-driven forms может быть удобнее чем reactive forms.
Vadem
Спасибо за ответ.
Тоже считаю, что получение формы через ViewChild неудобный и неочевидный способ.
Я не холивара ради, а понимания для.