Angular предоставляет три подхода к созданию форм: Signal Forms, Reactive Forms и Template-driven Forms. У каждого свои паттерны управления состоянием, валидацией и потоком данных. Это руководство помогает понять различия и выбрать подходящий подход для проекта.
Краткое сравнение
| Возможность | Signal Forms | Reactive Forms | Template-driven Forms |
|---|---|---|---|
| Источник истины | Пользовательская writable signal-модель | FormControl/FormGroup |
Модель пользователя в компоненте |
| Типобезопасность | Выводится из модели | Явная с typed forms | Минимальная |
| Валидация | Схема с path-based валидаторами | Список валидаторов, передаваемых Controls | На основе директив |
| Управление состоянием | На основе сигналов | На основе Observable | Управляется Angular |
| Настройка | Signal + функция схемы | Дерево FormControl | NgModel в шаблоне |
| Лучше всего для | Приложений на сигналах | Сложных форм | Простых форм |
| Кривая обучения | Средняя | Средняя–высокая | Низкая |
| Статус | Stable (v22+) | Stable | Stable |
На примере: форма входа
Лучший способ понять различия — увидеть одну и ту же форму, реализованную всеми тремя подходами.
Понимание различий
Три подхода делают разные проектные выборы, влияющие на то, как вы пишете и сопровождаете формы. Эти различия связаны с тем, где каждый подход хранит состояние формы и как управляет валидацией.
Где живут данные формы
Самое фундаментальное различие — где каждый подход считает «источником истины» значения формы.
Signal Forms хранят данные в writable signal. Когда нужны текущие значения формы, вы вызываете сигнал:
const credentials = this.loginModel(); // { email: '...', password: '...' }
Это держит данные формы в одном реактивном контейнере, который автоматически уведомляет Angular при изменении значений. Структура формы точно отражает модель данных.
Reactive Forms хранят данные внутри экземпляров FormControl и FormGroup. Значения доступны через иерархию формы:
const credentials = this.loginForm.value; // { email: '...', password: '...' }
Это отделяет управление состоянием формы от модели данных компонента. Структура формы явная, но требует больше кода настройки.
Template-driven Forms хранят данные в свойствах компонента. Значения доступны напрямую:
const credentials = {email: this.email, password: this.password};
Это самый прямой подход, но требует вручную собирать значения, когда они нужны. Angular управляет состоянием формы через директивы в шаблоне.
Как работает валидация
Каждый подход определяет правила валидации по-разному, влияя на то, где живёт логика валидации и как её сопровождать.
Signal Forms используют функцию схемы, где валидаторы привязываются к путям полей:
loginForm = form(this.loginModel, (fieldPath) => {
required(fieldPath.email, {message: 'Email is required'});
email(fieldPath.email, {message: 'Enter a valid email address'});
});
Все правила валидации живут вместе в одном месте. Функция схемы выполняется один раз при создании формы, а валидаторы запускаются автоматически при изменении значений полей. Сообщения об ошибках — часть определения валидации.
Reactive Forms прикрепляют валидаторы при создании controls:
loginForm = new FormGroup({
email: new FormControl('', [Validators.required, Validators.email]),
});
Валидаторы привязаны к отдельным controls в структуре формы. Это распределяет валидацию по определению формы. Сообщения об ошибках обычно живут в шаблоне.
Template-driven Forms используют атрибуты директив в шаблоне:
<input [(ngModel)]="email" required email />
Правила валидации живут в шаблоне рядом с HTML. Это держит валидацию близко к UI, но распределяет логику между шаблоном и компонентом.
Типобезопасность и автодополнение
Интеграция с TypeScript существенно различается между подходами, влияя на то, насколько компилятор помогает избегать ошибок.
Signal Forms выводят типы из структуры модели:
const loginModel = signal({email: '', password: ''});
const loginForm = form(loginModel);
// TypeScript knows: loginForm.email exists and returns FieldState<string>
Форма данных определяется один раз в сигнале, и TypeScript автоматически знает, какие поля существуют и их типы. Обращение к loginForm.username (которого нет) даёт ошибку типа.
Reactive Forms требуют явных аннотаций типов с typed forms:
const loginForm = new FormGroup({
email: new FormControl<string>(''),
password: new FormControl<string>(''),
});
// TypeScript knows: loginForm.controls.email is FormControl<string>
Типы указываются для каждого control отдельно. TypeScript проверяет структуру формы, но информацию о типах нужно поддерживать отдельно от модели данных.
Template-driven Forms предлагают минимальную типобезопасность:
email = '';
password = '';
// TypeScript only knows these are strings, no form-level typing
TypeScript понимает свойства компонента, но не знает о структуре формы или валидации. Проверка на этапе компиляции для операций с формой теряется.
Выбор подхода
Используйте Signal Forms, если:
- Вы создаёте новые приложения на сигналах (Angular v22+)
- Нужна типобезопасность, выводимая из структуры модели
- Вам близка валидация на основе схем
- Команда знакома с сигналами
Используйте Reactive Forms, если:
- Нужна production-ready стабильность
- Вы создаёте сложные динамические формы
- Предпочитаете паттерны на основе Observable
- Нужен тонкий контроль над состоянием формы
- Вы работаете с существующей кодовой базой reactive forms
Используйте Template-driven Forms, если:
- Вы создаёте простые формы (вход, контакт, поиск)
- Делаете быстрый прототип
- Логика формы прямолинейна
- Предпочитаете держать логику формы в шаблонах
- Вы работаете с существующей кодовой базой template-driven forms
Следующие шаги
Чтобы узнать больше о каждом подходе:
- Signal Forms: см. обзорное руководство для старта или углубитесь в модели форм, валидацию и управление состоянием полей
- Reactive Forms: см. руководство по Reactive Forms в документации Angular
- Template-driven Forms: см. руководство по Template-driven Forms в документации Angular