Формы в Angular
Обработка пользовательского ввода через формы — основа многих типичных приложений.
Приложения используют формы, чтобы пользователи могли войти в систему, обновить профиль, ввести конфиденциальные данные и выполнить множество других задач ввода данных.
Angular предлагает два подхода к обработке пользовательского ввода через формы: reactive и template-driven.
Оба захватывают события ввода из представления, валидируют ввод, создают форму и модель данных и предоставляют способ отслеживать изменения.
СОВЕТ: Если вы ищете новые Signal Forms, см. наше руководство по основам Signal Forms!
Это руководство помогает решить, какой тип форм лучше подходит вашей ситуации. В нём представлены общие строительные блоки обоих подходов. Также суммируются ключевые различия и демонстрируются они в контексте настройки, потока данных и тестирования.
Выбор подхода
Reactive forms и template-driven forms по-разному обрабатывают и управляют данными формы. У каждого подхода свои преимущества.
| Формы | Подробности |
|---|---|
| Reactive forms | Дают прямой, явный доступ к объектной модели формы. По сравнению с template-driven они более надёжны: масштабируемы, переиспользуемы и тестируемы. Если формы — ключевая часть приложения или вы уже используете реактивные паттерны, выбирайте reactive forms. |
| Template-driven forms | Опираются на директивы в шаблоне для создания и манипуляции объектной моделью. Полезны для простой формы в приложении, например подписки на email-рассылку. Их просто добавить, но они масштабируются хуже reactive forms. Если требования к форме очень базовые и логику можно вести только в шаблоне, template-driven forms могут подойти. |
Ключевые различия
Следующая таблица суммирует ключевые различия между reactive и template-driven forms.
| Reactive | Template-driven | |
|---|---|---|
| Настройка модели формы | Явная, создаётся в классе компонента | Неявная, создаётся директивами |
| Модель данных | Структурированная и неизменяемая | Неструктурированная и изменяемая |
| Поток данных | Синхронный | Асинхронный |
| Валидация формы | Функции | Директивы |
Масштабируемость
Если формы — центральная часть приложения, масштабируемость очень важна. Возможность переиспользовать модели форм между компонентами критична.
Reactive forms масштабируются лучше template-driven. Они дают прямой доступ к underlying form API и используют синхронный поток данных между представлением и моделью данных, что упрощает создание крупных форм. Reactive forms требуют меньше настройки для тестирования, и тестирование не требует глубокого понимания обнаружения изменений для корректной проверки обновлений и валидации.
Template-driven forms ориентированы на простые сценарии и менее переиспользуемы. Они абстрагируют underlying form API и используют асинхронный поток данных между представлением и моделью данных. Абстракция template-driven forms также влияет на тестирование. Тесты сильно зависят от ручного запуска обнаружения изменений и требуют больше настройки.
Настройка модели формы
И reactive, и template-driven forms отслеживают изменения значений между элементами ввода формы, с которыми взаимодействует пользователь, и данными формы в модели компонента. Оба подхода разделяют underlying строительные блоки, но различаются в том, как вы создаёте и управляете общими экземплярами form-control.
Общие базовые классы форм
И reactive, и template-driven forms построены на следующих базовых классах.
| Базовые классы | Подробности |
|---|---|
FormControl |
Отслеживает значение и статус валидации отдельного form control. |
FormGroup |
Отслеживает те же значения и статус для коллекции form controls. |
FormArray |
Отслеживает те же значения и статус для массива form controls. |
ControlValueAccessor |
Создаёт мост между экземплярами Angular FormControl и встроенными DOM-элементами. |
Настройка в reactive forms
В reactive forms модель формы определяется напрямую в классе компонента.
Директива [formControl] связывает явно созданный экземпляр FormControl с конкретным элементом формы в представлении через внутренний value accessor.
Следующий компонент реализует поле ввода для одного control с помощью reactive forms.
В этом примере модель формы — экземпляр FormControl.
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
@Component({
selector: 'app-reactive-favorite-color',
template: ` Favorite Color: <input type="text" [formControl]="favoriteColorControl" /> `,
imports: [ReactiveFormsModule],
})
export class FavoriteColorReactive {
favoriteColorControl = new FormControl('');
}
ВАЖНО: В reactive forms модель формы — источник истины; она предоставляет значение и статус элемента формы в любой момент через директиву [formControl] на элементе <input>.
Настройка в template-driven forms
В template-driven forms модель формы неявная, а не явная.
Директива NgModel создаёт и управляет экземпляром FormControl для данного элемента формы.
Следующий компонент реализует то же поле ввода для одного control с помощью template-driven forms.
import {Component, signal} from '@angular/core';
import {FormsModule} from '@angular/forms';
@Component({
selector: 'app-template-favorite-color',
template: ` Favorite Color: <input type="text" [(ngModel)]="favoriteColor" /> `,
imports: [FormsModule],
})
export class FavoriteColorTemplate {
favoriteColor = signal('');
}
ВАЖНО: В template-driven форме источник истины — шаблон. Директива NgModel автоматически управляет экземпляром FormControl за вас.
Поток данных в формах
Когда в приложении есть форма, Angular должен синхронизировать представление с моделью компонента и модель компонента с представлением. Когда пользователи меняют значения и делают выбор через представление, новые значения должны отражаться в модели данных. Аналогично, когда программная логика меняет значения в модели данных, эти значения должны отражаться в представлении.
Reactive и template-driven forms различаются в том, как обрабатывают поток данных от пользователя или от программных изменений. Следующие диаграммы иллюстрируют оба вида потока данных для каждого типа формы на примере поля favorite-color, определённого выше.
Поток данных в reactive forms
В reactive forms каждый элемент формы в представлении напрямую связан с моделью формы (экземпляром FormControl).
Обновления от представления к модели и от модели к представлению синхронны и не зависят от того, как рендерится UI.
Диаграмма view-to-model показывает, как текут данные при изменении значения поля ввода из представления:
- Пользователь вводит значение в элемент input, в данном случае любимый цвет Blue.
- Элемент ввода формы испускает событие "input" с последним значением.
ControlValueAccessor, слушающий события на элементе ввода формы, сразу передаёт новое значение экземпляруFormControl.- Экземпляр
FormControlиспускает новое значение через observablevalueChanges. - Любые подписчики на observable
valueChangesполучают новое значение.
Диаграмма model-to-view показывает, как программное изменение модели распространяется в представление:
- Пользователь вызывает метод
favoriteColorControl.setValue(), который обновляет значениеFormControl. - Экземпляр
FormControlиспускает новое значение через observablevalueChanges. - Любые подписчики на observable
valueChangesполучают новое значение. - Control value accessor на элементе ввода формы обновляет элемент новым значением.
Поток данных в template-driven forms
В template-driven forms каждый элемент формы связан с директивой, которая управляет моделью формы внутренне.
Диаграмма view-to-model показывает, как текут данные при изменении значения поля ввода из представления:
- Пользователь вводит Blue в элемент input.
- Элемент input испускает событие "input" со значением Blue.
- Control value accessor, прикреплённый к input, вызывает метод
setValue()на экземпляреFormControl. - Экземпляр
FormControlиспускает новое значение через observablevalueChanges. - Любые подписчики на observable
valueChangesполучают новое значение. - Control value accessor также вызывает метод
NgModel.viewToModelUpdate(), который испускает событиеngModelChange. - Поскольку шаблон компонента использует двустороннюю привязку данных для свойства
favoriteColor, свойствоfavoriteColorв компоненте обновляется значением, испущенным событиемngModelChange(Blue).
Диаграмма model-to-view показывает, как текут данные от модели к представлению при изменении favoriteColor с Blue на Red:
- Значение
favoriteColorобновляется в компоненте. - Начинается обнаружение изменений.
- Во время обнаружения изменений вызывается хук жизненного цикла
ngOnChangesна экземпляре директивыNgModel, потому что изменилось значение одного из её inputs. - Метод
ngOnChanges()ставит в очередь async-задачу для установки значения внутреннего экземпляраFormControl. - Обнаружение изменений завершается.
- На следующем тике выполняется задача установки значения экземпляра
FormControl. - Экземпляр
FormControlиспускает последнее значение через observablevalueChanges. - Любые подписчики на observable
valueChangesполучают новое значение. - Control value accessor обновляет элемент ввода формы в представлении последним значением
favoriteColor.
ПРИМЕЧАНИЕ: NgModel запускает второе обнаружение изменений, чтобы избежать ошибок ExpressionChangedAfterItHasBeenChecked, потому что изменение значения исходит из привязки input.
Изменяемость модели данных
Метод отслеживания изменений влияет на эффективность приложения.
| Формы | Подробности |
|---|---|
| Reactive forms | Сохраняют модель данных чистой, предоставляя её как неизменяемую структуру. При каждом изменении модели данных экземпляр FormControl возвращает новую модель данных, а не обновляет существующую. Это позволяет отслеживать уникальные изменения модели данных через observable control. Обнаружение изменений эффективнее, потому что обновляется только при уникальных изменениях. Поскольку обновления данных следуют реактивным паттернам, можно интегрироваться с операторами observable для преобразования данных. |
| Template-driven forms | Опираются на изменяемость с двусторонней привязкой данных для обновления модели данных в компоненте при изменениях в шаблоне. Поскольку при двусторонней привязке нет уникальных изменений для отслеживания в модели данных, обнаружение изменений менее эффективно при определении необходимости обновлений. |
Разница демонстрируется в предыдущих примерах с элементом ввода favorite-color.
- В reactive forms экземпляр
FormControlвсегда возвращает новое значение при обновлении значения control - В template-driven forms свойство favorite color всегда модифицируется до нового значения
Валидация формы
Валидация — неотъемлемая часть управления любым набором форм. Проверяете ли вы обязательные поля или запрашиваете внешний API на существующее имя пользователя, Angular предоставляет набор встроенных валидаторов, а также возможность создавать пользовательские.
| Формы | Подробности |
|---|---|
| Reactive forms | Определяют пользовательские валидаторы как функции, получающие control для валидации |
| Template-driven forms | Привязаны к директивам шаблона и должны предоставлять пользовательские директивы-валидаторы, оборачивающие функции валидации |
Подробнее см. Валидация форм.
Тестирование
Тестирование играет большую роль в сложных приложениях. Более простая стратегия тестирования полезна при проверке корректной работы форм. Reactive forms и template-driven forms по-разному зависят от рендера UI для утверждений на основе изменений form control и полей формы. Следующие примеры демонстрируют процесс тестирования форм с reactive и template-driven forms.
Тестирование reactive forms
Reactive forms дают относительно прямолинейную стратегию тестирования, потому что обеспечивают синхронный доступ к форме и моделям данных и могут тестироваться без рендера UI. В этих тестах статус и данные запрашиваются и манипулируются через control без взаимодействия с циклом обнаружения изменений.
Следующие тесты используют компоненты favorite-color из предыдущих примеров для проверки потоков данных view-to-model и model-to-view для reactive form.
Проверка потока данных view-to-model
Первый пример выполняет следующие шаги для проверки потока данных view-to-model.
- Запросить представление на элемент ввода формы и создать пользовательское событие "input" для теста.
- Установить новое значение input в Red и отправить событие "input" на элемент ввода формы.
- Утвердить, что значение
favoriteColorControlкомпонента совпадает со значением из input.
Favorite color test - view to model
it('should update the value of the input field', () => {
const input = fixture.nativeElement.querySelector('input');
const event = createNewEvent('input');
input.value = 'Red';
input.dispatchEvent(event);
expect(fixture.componentInstance.favoriteColorControl.value).toEqual('Red');
});
Следующий пример выполняет шаги для проверки потока данных model-to-view.
- Использовать
favoriteColorControl, экземплярFormControl, для установки нового значения. - Запросить представление на элемент ввода формы.
- Утвердить, что новое значение, установленное на control, совпадает со значением в input.
Favorite color test - model to view
it('should update the value in the control', () => {
component.favoriteColorControl.setValue('Blue');
const input = fixture.nativeElement.querySelector('input');
expect(input.value).toBe('Blue');
});
Тестирование template-driven forms
Написание тестов с template-driven forms требует детального знания процесса обнаружения изменений и понимания того, как директивы выполняются на каждом цикле, чтобы элементы запрашивались, тестировались или изменялись в правильное время.
Следующие тесты используют упомянутые ранее компоненты favorite color для проверки потоков данных от представления к модели и от модели к представлению для template-driven form.
Следующий тест проверяет поток данных от представления к модели.
Favorite color test - view to model
it('should update the favorite color in the component', async () => {
const input = fixture.nativeElement.querySelector('input');
const event = createNewEvent('input');
input.value = 'Red';
input.dispatchEvent(event);
await fixture.whenStable();
expect(component.favoriteColor()).toEqual('Red');
});
Шаги, выполненные в тесте view to model:
- Запросить представление на элемент ввода формы и создать пользовательское событие "input" для теста.
- Установить новое значение input в Red и отправить событие "input" на элемент ввода формы.
- Запустить обнаружение изменений через test fixture.
- Утвердить, что значение свойства
favoriteColorкомпонента совпадает со значением из input.
Следующий тест проверяет поток данных от модели к представлению.
Favorite color test - model to view
it('should update the favorite color on the input field', async () => {
component.favoriteColor.set('Blue');
await fixture.whenStable();
const input = fixture.nativeElement.querySelector('input');
expect(input.value).toBe('Blue');
});
Шаги, выполненные в тесте model to view:
- Использовать экземпляр компонента для установки значения свойства
favoriteColor. - Запустить обнаружение изменений через test fixture.
- Использовать
await fixture.whenStable(), чтобы дождаться следующего рендера. - Запросить представление на элемент ввода формы.
- Утвердить, что значение input совпадает со значением свойства
favoriteColorв экземпляре компонента.
Следующие шаги
Чтобы узнать больше о reactive forms, см. следующие руководства:
Чтобы узнать больше о template-driven forms, см. следующие руководства: