Angular
Reactive forms
Форма регистрации с динамическим списком навыков, где каждое поле зависит от соседнего, а кнопка отправки реагирует на статус валидности в реальном времени. В шаблонном подходе это превращается в клубок директив. Reactive forms переносят модель формы в код: дерево FormControl и FormGroup, которое можно собрать, провалидировать и протестировать без единой строки разметки. Состояние формы становится наблюдаемым потоком.
- Чекаут интернет-магазина: вложенные группы адреса, оплаты и доставки в одном FormGroup
- Конструкторы и редакторы: динамические FormArray для списков элементов произвольной длины
- Фильтры с автоприменением: подписка на valueChanges с debounceTime запускает запрос
- Многошаговые мастера: одна модель формы, разные представления на каждом шаге
- Связанные поля: подписка на valueChanges одного контрола включает или выключает другой
Предварительные знания
- Template-driven формы и понимание, почему их модель тяжело масштабировать
- Базовые RxJS-операторы: subscribe, pipe, debounceTime, map
- Знакомство с дженериками TypeScript и интерфейсами
Зачем понадобилась модель формы в коде
ReactiveFormsModule появился вместе с Angular 2 как ответ на ограничения template-driven форм. Идея: модель формы - это явная структура объектов в коде, а не побочный продукт разметки. В Angular 14 (2022) формы стали строго типизированными: FormGroup и FormControl получили дженерики, и значение формы перестало быть any. Это убрало целый класс ошибок. К Angular 21 реактивные формы остаются основным инструментом для сложных случаев, хотя для нового кода команда продвигает Signal Forms.
FormControl и FormGroup
FormControl - это единица модели: одно поле с текущим значением, статусом валидности и состояниями touched и dirty. FormGroup объединяет именованные контролы в одну структуру. Значение группы - объект, где ключи это имена контролов. Компонент импортирует ReactiveFormsModule.
Опция nonNullable: true делает контрол строго типизированным как string без null: при reset он вернётся к начальному значению, а не к null. Это убирает необходимость в обходных проверках на null и делает тип значения предсказуемым.
Директива [formGroup] связывает модель из кода с тегом form, а formControlName сопоставляет каждый input с контролом по имени. Разметка теперь лишь отображает модель, вся логика и структура живут в классе компонента.
Что даёт опция nonNullable: true у FormControl?
FormBuilder и FormArray
FormBuilder - сервис, сокращающий создание формы. Вместо new FormGroup и new FormControl используются fb.group, fb.control и fb.array. Получается через inject(FormBuilder) или inject(NonNullableFormBuilder), который по умолчанию делает все контролы nonNullable.
FormArray хранит список контролов переменной длины. Методы push и removeAt меняют состав в рантайме - именно это плохо давалось template-driven подходу. Геттер skills возвращает типизированный FormArray, чтобы шаблон итерировал по его controls.
NonNullableFormBuilder избавляет от повторения nonNullable: true на каждом контроле. Если для конкретного поля null всё же нужен (например, дата по умолчанию не выбрана), для него используется обычный fb.control с явным типом.
Какая структура реактивных форм подходит для списка полей переменной длины?
valueChanges и statusChanges
Каждый контрол и каждая группа предоставляют два Observable: valueChanges эмитит новое значение при каждом изменении, statusChanges - новый статус (VALID, INVALID, PENDING, DISABLED). Это и есть реактивная природа подхода: на изменения формы подписываются как на поток событий.
debounceTime гасит частый ввод, distinctUntilChanged отсекает повторы, а takeUntilDestroyed автоматически отписывается при уничтожении компонента. Без отписки подписка переживёт компонент и приведёт к утечке памяти и лишним запросам.
Подписка на valueChanges без отписки - частая причина утечек в реактивных формах. takeUntilDestroyed или преобразование в сигнал через toSignal решают это. Сигнал к тому же не требует ручной подписки и встраивается прямо в шаблон.
Почему подписку на valueChanges почти всегда сопровождают takeUntilDestroyed?
Связь с другими темами
Reactive forms - средний из трёх подходов, мост между template-driven и Signal Forms:
- Template-driven формы — Предыдущий подход, чьи ограничения по динамике и тестированию снимают реактивные формы
- Signal Forms — Следующая эволюция: модель на сигналах вместо Observable, более эргономичный API
- Валидация форм — Кастомные, асинхронные и кросс-полевые валидаторы поверх реактивной модели
Итог
- Модель реактивной формы живёт в коде как дерево FormControl, FormGroup и FormArray, требуется ReactiveFormsModule
- FormControl хранит одно значение, FormGroup - именованную группу контролов, FormArray - список переменной длины
- FormBuilder сокращает запись: fb.group, fb.control, fb.array вместо ручного new FormControl
- Типизированные формы (Angular 14+) дают типобезопасное значение формы без any и явных кастов
- valueChanges и statusChanges - это Observable, на которые подписываются для реакции на ввод и изменение валидности
Связанные уроки
- ng-29-template-forms — Реактивные формы решают проблемы шаблонного подхода, поэтому сначала нужно понимать его ограничения
- ng-31-signal-forms — Signal Forms - следующая эволюция: тот же контроль над моделью, но на сигналах вместо Observable
- ng-32-form-validation — Валидаторы реактивных форм раскрываются подробно: кастомные, асинхронные, кросс-полевые