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 — Валидаторы реактивных форм раскрываются подробно: кастомные, асинхронные, кросс-полевые
Reactive forms

0

1

Войти