В этом руководстве показано, как создать template-driven форму. Элементы управления формы привязаны к свойствам данных с валидацией ввода. Валидация помогает сохранять целостность данных, а стилизация — улучшать пользовательский опыт.
Template-driven формы используют двустороннюю привязку данных, чтобы обновлять модель данных в компоненте при изменениях в шаблоне и наоборот.
Template vs Reactive forms
Angular поддерживает два подхода к интерактивным формам. Template-driven формы позволяют использовать специфичные для форм директивы в шаблоне Angular. Reactive forms дают model-driven подход к построению форм.
Template-driven формы хорошо подходят для небольших или простых форм, а reactive forms более масштабируемы и удобны для сложных форм. Сравнение двух подходов см. в Выбор подхода
С помощью шаблона Angular можно построить почти любую форму — формы входа, контактные формы и практически любую бизнес-форму. Элементы управления можно размещать творчески и привязывать к данным объектной модели. Можно задавать правила валидации и показывать ошибки, условно разрешать ввод из конкретных контролов, включать встроенную визуальную обратную связь и многое другое.
Цели
Это руководство учит, как:
- Создать форму Angular с компонентом и шаблоном
- Использовать
ngModelдля двусторонней привязки данных для чтения и записи значений input-контролов - Давать визуальную обратную связь с помощью специальных CSS-классов, отслеживающих состояние контролов
- Показывать пользователям ошибки валидации и условно разрешать ввод из контролов формы на основе статуса формы
- Делиться информацией между HTML-элементами через template reference variables
Создание template-driven формы
Template-driven формы опираются на директивы, определённые в FormsModule.
| Директивы | Описание |
|---|---|
NgModel |
Согласует изменения значения привязанного элемента формы с изменениями модели данных, позволяя реагировать на ввод пользователя валидацией и обработкой ошибок. |
NgForm |
Создаёт экземпляр FormGroup верхнего уровня и привязывает его к элементу <form>, чтобы отслеживать агрегированное значение формы и статус валидации. Как только вы импортируете FormsModule, эта директива по умолчанию активна на всех тегах <form>. Специальный селектор добавлять не нужно. |
NgModelGroup |
Создаёт и привязывает экземпляр FormGroup к DOM-элементу. |
Обзор шагов
В ходе этого руководства вы привяжете пример формы к данным и обработаете ввод пользователя по следующим шагам.
- Создайте базовую форму.
- Определите пример модели данных
- Подключите необходимую инфраструктуру, например
FormsModule
- Привяжите контролы формы к свойствам данных через директиву
ngModelи синтаксис двусторонней привязки. - Отслеживайте валидность ввода и статус контрола через
ngModel.- Добавьте пользовательский CSS для визуальной обратной связи о статусе
- Показывайте и скрывайте сообщения об ошибках валидации
- Реагируйте на нативное событие клика HTML-кнопки, добавляя данные в модель.
- Обработайте отправку формы через output-свойство
ngSubmitформы.- Отключайте кнопку Submit, пока форма невалидна
- После отправки замените заполненную форму другим содержимым на странице
Создание формы
В предоставленном примере приложения создаётся класс
Actor, который определяет модель данных, отражённую в форме.actor.ts
export class Actor { constructor( public id: number, public name: string, public skill: string, public studio?: string, ) {} }Разметка и детали формы определены в классе
ActorFormComponent.actor-form.component.ts (v1)
import {Component} from '@angular/core'; import {Actor} from '../actor'; import {FormsModule} from '@angular/forms'; import {JsonPipe} from '@angular/common'; @Component({ selector: 'app-actor-form', templateUrl: './actor-form.component.html', imports: [FormsModule, JsonPipe], }) export class ActorFormComponent { skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting']; model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions'); submitted = false; onSubmit() { this.submitted = true; } }Значение
selectorкомпонента"app-actor-form"означает, что форму можно вставить в родительский шаблон тегом<app-actor-form>.Следующий код создаёт новый экземпляр актёра, чтобы начальная форма могла показать пример актёра.
const myActress = new Actor(42, 'Marilyn Monroe', 'Singing'); console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn"В этой демонстрации для
modelиskillsиспользуются фиктивные данные. В реальном приложении вы бы внедрили сервис данных для получения и сохранения реальных данных или открыли эти свойства как inputs и outputs.Компонент включает возможность Forms, импортируя модуль
FormsModule.@Component({ selector: 'app-actor-form', templateUrl: './actor-form.component.html', imports: [FormsModule, JsonPipe], }) export class ActorFormComponent {Форма отображается в макете приложения, определённом шаблоном корневого компонента.
app.component.html
<app-actor-form />Начальный шаблон задаёт макет формы с двумя группами полей и кнопкой отправки. Группы полей соответствуют двум свойствам модели данных Actor: name и studio. У каждой группы есть метка и поле для ввода пользователя.
- У
<input>Name есть HTML5-атрибутrequired - У
<input>Studio его нет, потому чтоstudioнеобязателен
У кнопки Submit есть классы для стилизации. На этом этапе макет формы — обычный HTML5 без привязок и директив.
- У
Пример формы использует несколько классов стилей из Twitter Bootstrap:
container,form-group,form-controlиbtn. Чтобы использовать эти стили, таблица стилей приложения импортирует библиотеку.styles.css
@import url('https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css');Форма требует, чтобы навык актёра выбирался из предопределённого списка
skills, хранящегося внутриActorFormComponent. Цикл Angular@forперебирает значения данных, чтобы заполнить элемент<select>.actor-form.component.html (skills)
<div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div>
Если запустить приложение прямо сейчас, в элементе выбора виден список навыков. Элементы ввода ещё не привязаны к значениям данных или событиям, поэтому они пусты и не имеют поведения.
Привязка input-контролов к свойствам данных
Следующий шаг — привязать input-контролы к соответствующим свойствам Actor двусторонней привязкой данных, чтобы они реагировали на ввод пользователя обновлением модели данных и на программные изменения данных — обновлением отображения.
Директива ngModel, объявленная в FormsModule, позволяет привязывать контролы в template-driven форме к свойствам модели данных.
Когда директива включается синтаксисом двусторонней привязки [(ngModel)], Angular может отслеживать значение и взаимодействие пользователя с контролом и синхронизировать представление с моделью.
- Отредактируйте файл шаблона
actor-form.component.html. - Найдите тег
<input>рядом с меткой Name. - Добавьте директиву
ngModelс синтаксисом двусторонней привязки[(ngModel)]="...".
actor-form.component.html (excerpt)
<input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" />
TODO: remove this: {{ model.name }}
ПОЛЕЗНО: В этом примере после каждого тега input временно стоит диагностическая интерполяция {{model.name}}, чтобы показать текущее значение соответствующего свойства. Комментарий напоминает удалить диагностические строки, когда вы закончите наблюдать работу двусторонней привязки.
Доступ к общему статусу формы
Когда вы импортировали FormsModule в компонент, Angular автоматически создал и прикрепил директиву NgForm к тегу <form> в шаблоне (потому что у NgForm селектор form, совпадающий с элементами <form>).
Чтобы получить доступ к NgForm и общему статусу формы, объявите template reference variable.
Отредактируйте файл шаблона
actor-form.component.html.Обновите тег
<form>template reference variable#actorFormи задайте её значение следующим образом.actor-form.component.html (excerpt)
<form #actorForm="ngForm">Template variable
actorFormтеперь ссылается на экземпляр директивыNgForm, управляющий формой в целом.Запустите приложение.
Начните вводить текст в поле Name.
По мере добавления и удаления символов вы видите, как они появляются и исчезают в модели данных.
Диагностическая строка с интерполированными значениями показывает, что значения действительно текут из поля ввода в модель и обратно.
Именование элементов управления
Когда на элементе используется [(ngModel)], для этого элемента нужно определить атрибут name.
Angular использует назначенное имя, чтобы зарегистрировать элемент у директивы NgForm, прикреплённой к родительскому элементу <form>.
В примере к элементу <input> добавлен атрибут name со значением "name", что логично для имени актёра.
Подойдёт любое уникальное значение, но описательное имя удобнее.
- Добавьте аналогичные привязки
[(ngModel)]и атрибутыnameк Studio и Skill. - Теперь можно удалить диагностические сообщения с интерполированными значениями.
- Чтобы подтвердить, что двусторонняя привязка работает для всей модели актёра, добавьте в начало шаблона компонента новую текстовую привязку с pipe
json, которая сериализует данные в строку.
После этих правок шаблон формы должен выглядеть так:
actor-form.component.html (excerpt)
{{ model | json }}
<div class="form-group">
<label for="name">Name</label>
<input
type="text"
class="form-control"
id="name"
required
[(ngModel)]="model.name"
name="name"
/>
</div>
<div class="form-group">
<label for="studio">Studio</label>
<input
type="text"
class="form-control"
id="studio"
[(ngModel)]="model.studio"
name="studio"
/>
</div>
<div class="form-group">
<label for="skill">Skill</label>
<select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill">
@for (skill of skills; track $index) {
<option [value]="skill">{{ skill }}</option>
}
</select>
</div>
Обратите внимание:
У каждого элемента
<input>есть свойствоid. Его использует атрибутforэлемента<label>, чтобы связать метку с input-контролом. Это стандартная возможность HTML.У каждого элемента
<input>также есть обязательное свойствоname, которое Angular использует для регистрации контрола в форме.
Когда вы наблюдали эффекты, текстовую привязку {{ model | json }} можно удалить.
Отслеживание состояний формы
Angular применяет класс ng-submitted к элементам form после отправки формы. Этот класс можно использовать, чтобы изменить стиль формы после отправки.
Отслеживание состояний контрола
Добавление директивы NgModel к контролу добавляет к нему имена классов, описывающие его состояние.
Эти классы можно использовать, чтобы менять стиль контрола в зависимости от состояния.
В следующей таблице описаны имена классов, которые Angular применяет в зависимости от состояния контрола.
| Состояния | Класс если true | Класс если false |
|---|---|---|
| Контрол был посещён. | ng-touched |
ng-untouched |
| Значение контрола изменилось. | ng-dirty |
ng-pristine |
| Значение контрола валидно. | ng-valid |
ng-invalid |
Angular также применяет класс ng-submitted к элементам form при отправке, но не к контролам внутри элемента form.
Эти CSS-классы используют, чтобы задать стили контрола в зависимости от его статуса.
Наблюдение за состояниями контрола
Чтобы увидеть, как фреймворк добавляет и удаляет классы, откройте инструменты разработчика браузера и осмотрите элемент <input>, представляющий имя актёра.
С помощью инструментов разработчика браузера найдите элемент
<input>, соответствующий полю Name. Видно, что у элемента несколько CSS-классов в дополнение к "form-control".При первом открытии классы указывают, что значение валидно, не менялось с инициализации или сброса и контрол не посещался с инициализации или сброса.
<input class="form-control ng-untouched ng-pristine ng-valid" />Выполните следующие действия с полем Name
<input>и наблюдайте, какие классы появляются.Посмотрите, но не трогайте. Классы указывают, что контрол untouched, pristine и valid.
Кликните внутри поля имени, затем кликните снаружи. Контрол теперь посещён, и у элемента класс
ng-touchedвместоng-untouched.Добавьте слэши в конец имени. Теперь контрол touched и dirty.
Сотрите имя. Значение становится невалидным, поэтому класс
ng-invalidзаменяетng-valid.
Визуальная обратная связь для состояний
Пара ng-valid/ng-invalid особенно интересна, потому что при невалидных значениях нужен
сильный визуальный сигнал.
Также нужно отмечать обязательные поля.
Обязательные поля и невалидные данные можно отметить одновременно цветной полосой слева от поля ввода.
Чтобы изменить внешний вид таким образом, выполните следующие шаги.
Добавьте определения для CSS-классов
ng-*.Добавьте эти определения классов в новый файл
forms.css.Добавьте новый файл в проект рядом с
index.html:forms.css
.ng-valid[required], .ng-valid.required { border-left: 5px solid #42A948; /* green */ } .ng-invalid:not(form) { border-left: 5px solid #a94442; /* red */ }В файле
index.htmlобновите тег<head>, чтобы подключить новую таблицу стилей.index.html (styles)
<link rel="stylesheet" href="assets/forms.css" />
Показ и скрытие сообщений об ошибках валидации
Поле Name обязательно, и его очистка делает полосу красной. Это указывает, что что-то не так, но пользователь не знает, что именно и что делать. Можно дать полезное сообщение, проверяя состояние контрола и реагируя на него.
Выпадающий список Skill тоже обязателен, но ему не нужна такая обработка ошибок, потому что список уже ограничивает выбор валидными значениями.
Чтобы определить и показать сообщение об ошибке в нужный момент, выполните следующие шаги.
-
Add a local reference to the input
Расширьте тег
inputtemplate reference variable, чтобы из шаблона обращаться к Angular-контролу поля ввода. В примере переменная —#name="ngModel".Template reference variable (
#name) задаётся как"ngModel", потому что это значение свойстваNgModel.exportAs. Это свойство говорит Angular, как связать reference variable с директивой. -
Add the error message
Добавьте
<div>с подходящим сообщением об ошибке. -
Make the error message conditional
Показывайте или скрывайте сообщение об ошибке, привязав свойства контрола
nameк свойствуhiddenэлемента<div>сообщения. -
Add a conditional error message to name
Добавьте условное сообщение об ошибке к полю
name, как в следующем примере.
actor-form.component.html (hidden-error-msg)
<div [hidden]="name.valid || name.pristine" class="alert alert-danger">
В этом примере сообщение скрывается, когда контрол либо валиден, либо pristine.
Pristine означает, что пользователь не менял значение с момента отображения в этой форме.
Если игнорировать состояние pristine, сообщение скрывалось бы только при валидном значении.
Если попасть в этот компонент с новым пустым актёром или невалидным актёром, сообщение об ошибке появится сразу, до любых действий.
Возможно, сообщение нужно показывать только когда пользователь делает невалидное изменение.
Скрытие сообщения, пока контрол в состоянии pristine, достигает этой цели.
Значимость этого выбора станет ясна, когда в следующем шаге вы добавите нового актёра в форму.
Добавление нового актёра
Это упражнение показывает, как реагировать на нативное событие клика HTML-кнопки, добавляя данные в модель. Чтобы пользователи формы могли добавить нового актёра, добавьте кнопку New Actor, реагирующую на событие click.
В шаблоне разместите элемент
<button>"New Actor" внизу формы.В файле компонента добавьте метод создания актёра в модель данных актёра.
actor-form.component.ts (New Actor method)
newActor() { this.model = new Actor(42, '', ''); }Привяжите событие click кнопки к методу создания актёра
newActor().actor-form.component.html (New Actor button)
<button type="button" class="btn btn-default" (click)="newActor()">New Actor</button>Снова запустите приложение и нажмите кнопку New Actor.
Форма очищается, и обязательные полосы слева от полей ввода красные, указывая на невалидные свойства
nameиskill. Обратите внимание, что сообщения об ошибках скрыты. Это потому, что форма pristine: вы ещё ничего не меняли.Введите имя и снова нажмите New Actor.
Теперь приложение показывает сообщение об ошибке
Name is required, потому что поле ввода больше не pristine. Форма помнит, что вы вводили имя до нажатия New Actor.Чтобы восстановить pristine-состояние контролов формы, сбросьте все флаги императивно, вызвав метод формы
reset()после вызоваnewActor().actor-form.component.html (Reset the form)
<button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()"> New Actor </button>Теперь нажатие New Actor сбрасывает и форму, и флаги её контролов.
Отправка формы с ngSubmit
Пользователь должен иметь возможность отправить эту форму после заполнения.
Кнопка Submit внизу формы сама по себе ничего не делает, но запускает событие отправки формы из-за своего типа (type="submit").
Чтобы отреагировать на это событие, выполните следующие шаги.
-
Listen to ngOnSubmit
Привяжите событие
ngSubmitформы к методуonSubmit()компонента actor-form.actor-form.component.html (ngSubmit)
<form (ngSubmit)="onSubmit()" #actorForm="ngForm"> -
Bind the disabled property
Используйте template reference variable
#actorForm, чтобы получить доступ к форме, содержащей кнопку Submit, и создайте привязку события.Привяжите свойство формы, указывающее на её общую валидность, к свойству
disabledкнопки Submit.actor-form.component.html (submit-button)
<button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid"> Submit </button> -
Run the application
Обратите внимание, что кнопка включена — хотя пока ничего полезного не делает.
-
Delete the Name value
Это нарушает правило "required", поэтому показывается сообщение об ошибке — и обратите внимание, что кнопка Submit также отключается.
Не пришлось явно связывать состояние включения кнопки с валидностью формы.
FormsModuleсделал это автоматически, когда вы определили template reference variable на расширенном элементе формы, а затем сослались на эту переменную в контроле кнопки.
Реакция на отправку формы
Чтобы показать реакцию на отправку формы, можно скрыть область ввода данных и отобразить что-то другое на её месте.
-
Wrap the form
Оберните всю форму в
<div>и привяжите его свойствоhiddenк свойствуActorFormComponent.submitted.actor-form.component.html (excerpt)
<div [hidden]="submitted"> <h1>Actor Form</h1> <form (ngSubmit)="onSubmit()" #actorForm="ngForm"> <!-- ... all of the form ... --> </form> </div>Основная форма видна с самого начала, потому что свойство
submittedравно false, пока вы не отправите форму, как показывает этот фрагмент изActorFormComponent:actor-form.component.ts (submitted)
submitted = false; onSubmit() { this.submitted = true; }Когда вы нажимаете кнопку Submit, флаг
submittedстановится true, и форма исчезает. -
Add the submitted state
Чтобы показать что-то другое, пока форма в состоянии submitted, добавьте следующий HTML ниже новой обёртки
<div>.actor-form.component.html (excerpt)
<div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Studio</div> <div class="col-xs-9">{{ model.studio }}</div> </div> <div class="row"> <div class="col-xs-3">Skill</div> <div class="col-xs-9">{{ model.skill }}</div> </div> <br /> <button type="button" class="btn btn-primary" (click)="submitted = false">Edit</button> </div>Этот
<div>, показывающий актёра только для чтения с интерполяционными привязками, появляется только пока компонент в состоянии submitted.Альтернативное отображение включает кнопку Edit, событие click которой привязано к выражению, сбрасывающему флаг
submitted. -
Test the Edit button
Нажмите кнопку Edit, чтобы вернуть отображение к редактируемой форме.
Итог
Форма Angular, рассмотренная на этой странице, использует следующие возможности фреймворка для поддержки изменения данных, валидации и многого другого.
- HTML-шаблон формы Angular
- Класс компонента формы с декоратором
@Component - Обработка отправки формы привязкой к событию
NgForm.ngSubmit - Template-reference variables, такие как
#actorFormи#name - Синтаксис
[(ngModel)]для двусторонней привязки данных - Использование атрибутов
nameдля валидации и отслеживания изменений элементов формы - Свойство
validreference variable на input-контролах указывает, валиден ли контрол или нужно показать сообщения об ошибках - Управление состоянием включения кнопки Submit привязкой к валидности
NgForm - Пользовательские CSS-классы, дающие пользователям визуальную обратную связь о невалидных контролах
Вот код финальной версии приложения: