1. Знакомство с Reactive Forms
Reactive Forms — это подход к созданию форм в Angular, при котором все данные, структура и логика формы описываются в TypeScript-коде, а не только в шаблоне. Если Template-driven формы можно сравнить с автоматической коробкой передач ("Angular сам за тебя всё рулит, а ты только указываешь, что куда привязать"), то Reactive Forms — это механика: ты сам управляешь каждым элементом, но получаешь полный контроль.
Когда стоит использовать Reactive Forms?
- Когда форма большая, сложная или динамически меняется (например, появляются или исчезают поля).
- Когда нужна сложная валидация, зависящая от других полей или внешних условий.
- Когда хочется тестировать формы unit-тестами.
- Когда нужно управлять состоянием формы из кода (например, сбрасывать/заполнять формы, динамически строить их структуру).
Забавный факт:
Reactive Forms построены по принципу реактивного программирования. Все изменения формы — это потоки событий, которые можно слушать, на них можно подписываться и обрабатывать их, как настоящие программисты будущего.
2. Как подключить ReactiveFormsModule
Перед тем как начать творить чудеса с реактивными формами, нужно подключить специальный модуль — ReactiveFormsModule. Без него Angular даже не поймёт, что вы хотите от жизни.
Шаг 1. Импорт модуля
Откройте файл модуля вашего приложения, обычно это app.module.ts, и добавьте импорт:
import { ReactiveFormsModule } from '@angular/forms';
Шаг 2. Добавление в массив imports
В том же файле найдите массив imports внутри декоратора @NgModule и добавьте туда ReactiveFormsModule:
@NgModule({
declarations: [
// ... ваши компоненты
],
imports: [
// ... другие модули
ReactiveFormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Всё! Теперь вы готовы к созданию реактивных форм. Если забыть этот шаг, Angular будет жаловаться на непонятные директивы вроде formControlName и будет обижаться на отсутствие формы.
3. Базовые строительные блоки Reactive Forms
В реактивных формах всё крутится вокруг трёх классов:
- FormControl — управляет состоянием и значением отдельного поля ввода.
- FormGroup — объединяет несколько FormControl в логическую группу (например, вся форма).
- FormArray — массив FormControl или FormGroup, для динамических списков (например, список телефонов).
Краткая аналогия
- FormControl — как отдельный переключатель.
- FormGroup — как панель с кучей переключателей.
- FormArray — как ряд одинаковых переключателей, которые можно добавлять или удалять.
Для начала мы сосредоточимся на FormControl и FormGroup.
4. Первый пример: простая форма с одним полем
Давайте создадим форму с одним полем (например, имя пользователя) и научимся управлять этим полем из кода.
Импортируем нужные классы
В компоненте, где будет форма, импортируем классы:
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
Создаём FormControl
export class SimpleFormComponent {
name = new FormControl(''); // начальное значение — пустая строка
}
Связываем FormControl с шаблоном
<form>
<input type="text" [formControl]="name" placeholder="Введите имя">
</form>
<p>Вы ввели: {{ name.value }}</p>
Примечание:
Здесь мы используем квадратные скобки: [formControl]="name", потому что связываем свойство компонента и элемент формы.
Как это работает?
- Когда пользователь вводит текст, значение автоматически обновляется в объекте name.
- Когда вы меняете значение через код (this.name.setValue('Гермиона')), оно появляется в поле ввода.
5. Пример сложнее: форма с несколькими полями (FormGroup)
В реальных приложениях формы почти всегда содержат больше одного поля. Для этого используют FormGroup.
Импортируем FormGroup
import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
Создаём FormGroup в компоненте
export class LoginFormComponent {
loginForm = new FormGroup({
email: new FormControl(''),
password: new FormControl('')
});
}
Связываем FormGroup с шаблоном
<form [formGroup]="loginForm">
<label>
Email:
<input type="email" formControlName="email">
</label>
<br>
<label>
Пароль:
<input type="password" formControlName="password">
</label>
<br>
<button type="submit">Войти</button>
</form>
<p>Текущее значение формы: {{ loginForm.value | json }}</p>
Как это работает?
- Директива [formGroup]="loginForm" говорит Angular, что вся форма управляется этим объектом.
- Каждый formControlName="..." связывает поле ввода с соответствующим контролом в группе.
- loginForm.value — это объект с текущими значениями всех полей.
6. Обработка событий отправки формы
Реактивная форма не отправляется "по-настоящему" (как в старых добрых HTML-формах), если не обработать событие отправки.
Обработка submit
В шаблоне:
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
<!-- ...поля... -->
<button type="submit">Войти</button>
</form>
В компоненте:
onSubmit() {
console.log('Данные формы:', this.loginForm.value);
}
Важно:
Событие ngSubmit сработает только если кнопка имеет type="submit", и форма находится внутри тега <form>. Не забывайте эти детали!
7. Валидация: встроенные валидаторы
Reactive Forms позволяют легко добавлять валидацию прямо в конструкторе формы.
Импортируем Validators
import { Validators } from '@angular/forms';
Добавляем валидаторы
loginForm = new FormGroup({
email: new FormControl('', [Validators.required, Validators.email]),
password: new FormControl('', [Validators.required, Validators.minLength(6)])
});
- Validators.required — поле обязательно.
- Validators.email — значение должно быть похоже на email.
- Validators.minLength(6) — минимум 6 символов.
Проверка ошибок в шаблоне
<input type="email" formControlName="email">
<div *ngIf="loginForm.get('email')?.invalid && loginForm.get('email')?.touched">
<small *ngIf="loginForm.get('email')?.errors?.['required']">Email обязателен</small>
<small *ngIf="loginForm.get('email')?.errors?.['email']">Некорректный email</small>
</div>
8. Практический пример: форма регистрации
Давайте соберём всё вместе и создадим простую форму регистрации:
Компонент
import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
@Component({
selector: 'app-register-form',
templateUrl: './register-form.component.html'
})
export class RegisterFormComponent {
registerForm = new FormGroup({
username: new FormControl('', [Validators.required]),
email: new FormControl('', [Validators.required, Validators.email]),
password: new FormControl('', [Validators.required, Validators.minLength(8)])
});
onSubmit() {
if (this.registerForm.valid) {
console.log('Регистрация:', this.registerForm.value);
// Здесь может быть отправка данных на сервер
} else {
console.log('Форма невалидна!');
}
}
}
Шаблон
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
<label>
Имя пользователя:
<input type="text" formControlName="username">
</label>
<div *ngIf="registerForm.get('username')?.invalid && registerForm.get('username')?.touched">
<small>Имя обязательно</small>
</div>
<label>
Email:
<input type="email" formControlName="email">
</label>
<div *ngIf="registerForm.get('email')?.invalid && registerForm.get('email')?.touched">
<small *ngIf="registerForm.get('email')?.errors?.['required']">Email обязателен</small>
<small *ngIf="registerForm.get('email')?.errors?.['email']">Некорректный email</small>
</div>
<label>
Пароль:
<input type="password" formControlName="password">
</label>
<div *ngIf="registerForm.get('password')?.invalid && registerForm.get('password')?.touched">
<small *ngIf="registerForm.get('password')?.errors?.['required']">Пароль обязателен</small>
<small *ngIf="registerForm.get('password')?.errors?.['minlength']">Минимум 8 символов</small>
</div>
<button type="submit" [disabled]="registerForm.invalid">Зарегистрироваться</button>
</form>
9. Схема: как работает реактивная форма
[FormGroup] --- (username: FormControl)
|
+--- (email: FormControl)
|
+--- (password: FormControl)
Пояснение:
- Все поля объединены в одну группу.
- Значения и валидность формы доступны через объект FormGroup.
- Любое изменение поля сразу отражается в состоянии всей формы.
10. Типичные ошибки при подключении и использовании Reactive Forms
Ошибка №1: забыли импортировать ReactiveFormsModule.
Angular не понимает директивы formGroup, formControlName, formControl и кидает ошибку вида "Can't bind to 'formGroup' since it isn't a known property...". Проверьте, что в вашем модуле есть импорт ReactiveFormsModule.
Ошибка №2: не используете FormGroup, а пытаетесь сразу писать formControlName.
formControlName работает только внутри формы, связанной с [formGroup]. Если забыли добавить [formGroup]="...", Angular не сможет найти контекст.
Ошибка №3: не инициализированы все контролы в FormGroup.
Если в шаблоне есть поле с formControlName="email", а в FormGroup забыли его добавить — будет ошибка "Cannot find control with name: 'email'".
Ошибка №4: неправильное использование formControl (без квадратных скобок).
Вместо [formControl]="name" случайно пишут formControl="name", и Angular воспринимает "name" как строку, а не как переменную.
Ошибка №5: забыли обработать отправку формы через (ngSubmit).
Если не добавить обработчик (ngSubmit), то форма не будет реагировать на Enter и submit, а просто перезагрузит страницу.
Ошибка №6: не обновляете значения формы программно.
В Reactive Forms значения нужно менять через методы .setValue() или .patchValue(), а не напрямую присваивать.
Ошибка №7: забыли отключить кнопку отправки при невалидной форме.
Пользователь может отправить пустую или некорректную форму, если не добавить [disabled]="registerForm.invalid" к кнопке.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ