Russian (Pусский) translation by Masha Kolesnikova (you can also view the original English article)

Формы имеют решающее значение для любого современного front-end приложения, и они являются функцией, которую мы используем каждый день, даже если этого не осознаем. Формы необходимы для безопасного входа пользователя в приложение, поиска всех доступных отелей в определенном городе, бронирования такси, составления списка дел и выполнения множества других вещей, к которым мы привыкли. В некоторых формах есть только несколько полей ввода, тогда как другие формы могут иметь массив полей, которые простираются до нескольких страниц или вкладок.
В этом уроке мы поговорим о различных стратегиях, доступных для разработки форм в Angular. Независимо от выбранной вами стратегии, вот те вещи, которые должна покрывать библиотека формы:
- Поддержка двухсторонней привязки, чтобы значения управления входом синхронизировались с состоянием компонента.
- Следить за состоянием формы и использовать визуальные сигналы, чтобы дать пользователю знать, валидное ли текущее состояние или нет. Например, если имя пользователя имеет недопустимые символы, то вокруг поля ввода для имени пользователя должна появиться красная рамка.
- Иметь механизм для правильного отображения ошибок валидации.
- Включать или отключать определенные части формы, если не соблюдены некоторые критерии валидации.
Введение в формы Angular
Angular, являющийся полнофункциональным front-end фреймворком, имеет свой собственный набор библиотек для построения сложных форм. Последняя версия Angular имеет две мощные стратегии построения форм. Вот они:
- шаблонные формы
- модельные или реактивные формы
Обе технологии принадлежат библиотеке @angular/forms
и основаны на тех же классах управления формой. Тем не менее, они значительно отличаются в своей философии, стиле программирования и технике. Выбор одного из них зависит от вашего личного вкуса, а также от сложности формы, которую вы пытаетесь создать. На мой взгляд, сначала вы должны попробовать оба подхода, а затем выбрать тот, который соответствует вашему стилю и проекту.
В первой части учебника будут рассмотрены шаблонные формы с практическим примером: создание формы регистрации с валидацией для всех полей формы. Во второй части этого урока мы вернемся на пару шагов назад, чтобы создать ту же форму, используя подход, основанный на модели.
Шаблонные формы
Шаблонный подход - это стратегия, которая была заимствована из эпохи AngularJS. На мой взгляд, это самый простой метод построения форм. Как это работает? Мы будем использовать некоторые директивы Agnular.
Директивы позволяют прикрепить поведение к элементам в DOM.
- Документация Angular
Angular предоставляет директивы, специфичные для конкретной формы, которые вы можете использовать для привязки входных данных формы и модели. Директивы, специфичные для формы, добавляют дополнительную функциональность и поведение к простой форме HTML. Конечным результатом является то, что шаблон заботится о привязке значений к модели и проверке формы.
В этом уроке мы будем использовать шаблонные формы для создания страницы регистрации в приложении. Форма будет охватывать наиболее распространенные элементы формы и различные проверки валидации этих элементов формы. Вот шаги, которые вы проследуете в этом уроке.
- Добавьте FormsModule в
app.module.ts
. - Создайте класс для модели User.
- Создайте исходные компоненты и макет для формы регистрации.
- Используйте Angular директивы формы, такие как
ngModel
,ngModelGroup
иngForm
. - Добавьте валидацию с помощью встроенных валидаторов.
- Отображать ошибки валидации.
- Управление формой с помощью
ngSubmit
.
Давайте начнем.
Предпосылки
Код для этого проекта доступен в моем репозитории GitHub. Загрузите zip-архиов или клонируйте репо, чтобы увидеть его в действии. Если вы предпочитаете начинать с нуля, убедитесь, что у вас установлен Angular CLI. Используйте команду ng
для создания нового проекта.
$ ng new SignupFormProject
Затем создайте новый компонент для RegisterForm.
ng generate component SignupForm
Замените содержимое app.component.html следующим образом:
<app-signup-form> </app-signup-form>
Вот структура каталогов для каталога src/. Я удалил некоторые несущественные файлы, чтобы все было просто.
. ├── app │ ├── app.component.css │ ├── app.component.html │ ├── app.component.ts │ ├── app.module.ts │ ├── signup-form │ │ ├── signup-form.component.css │ │ ├── signup-form.component.html │ │ └── signup-form.component.ts │ └── User.ts ├── index.html ├── main.ts ├── polyfills.ts ├── styles.css ├── tsconfig.app.json └── typings.d.ts
Как вы можете видеть, каталог для компонента RegisterForm
был создан автоматически. Вот где будет находится большая часть нашего кода. Я также создал новый User.ts
для хранения нашей модели User.
Шаблон HTML
Прежде чем мы погрузимся в шаблон фактического компонента, нам нужно иметь абстрактное представление о том, что мы строим. Итак, вот структура формы, которая у меня на уме. Форма регистрации будет содержать несколько полей ввода, элемент выбора и элемент флажка.
Вот HTML-шаблон, который мы будем использовать для нашей страницы регистрации.
Шаблон HTML
<div class="row custom-row"> <div class= "col-sm-5 custom-container jumbotron"> <form class="form-horizontal"> <fieldset> <legend>SignUp</legend> <!--- Email Block ---> <div class="form-group"> <label for="inputEmail">Email</label> <input type="text" id="inputEmail" placeholder="Email"> </div> <!--- Password Block ---> <div class="form-group"> <label for="inputPassword">Password</label> <input type="password" id="inputPassword" placeholder="Password"> </div> <div class="form-group"> <label for="confirmPassword" >Confirm Password</label> <input type="password" id="confirmPassword" placeholder="Password"> </div> <!--- Select gender Block ---> <div class="form-group"> <label for="select">Gender</label> <select id="select"> <option>Male</option> <option>Female</option> <option>Other</option> </select> </div> <!--- Terms and conditions Block ---> <div class="form-group checkbox"> <label> <input type="checkbox"> Confirm that you've read the Terms and Conditions </label> </div> <!--- Buttons Block ---> <div class="form-group"> <button type="reset" class="btn btn-default">Cancel</button> <button type="submit" class="btn btn-primary">Submit</button> </div> </fieldset> </form> </div> </div>
Классы CSS, используемые в шаблоне HTML, являются частью библиотеки Bootstrap. Поскольку это не учебник по дизайну, я не буду здесь слишком много говорить о CSS-аспектах формы, если это необходимо.
Настройка базовой формы
Чтобы использовать директивы формы с шаблоном, нам нужно импортировать FormsModule
из @angular/forms
и добавить его в массив import
в app.module.ts
.
app/app.module.ts
import { FormsModule } from '@angular/forms'; @NgModule({ . . imports: [ BrowserModule, FormsModule ], . . }) export class AppModule { }
Затем создайте класс, который будет содержать все свойства объекта User. Мы можем использовать интерфейс и реализовать его в компоненте или использовать класс TypeScript для модели.
app/User.ts
export class User { id: number; email: string; //Both the passwords are in a single object password: { pwd: string; confirmPwd: string; }; gender: string; terms: boolean; constructor(values: Object = {}) { //Constructor initialization Object.assign(this, values); } }
Теперь создайте экземпляр класса в компоненте RegistrationForm. Я также объявил дополнительное свойство для пола.
app/signup-form/signup-form.component.ts
import { Component, OnInit } from '@angular/core'; // Import the User model import { User } from './../User'; @Component({ selector: 'app-signup-form', templateUrl: './signup-form.component.html', styleUrls: ['./signup-form.component.css'] }) export class SignupFormComponent implements OnInit { //Property for the gender private gender: string[]; //Property for the user private user:User; ngOnInit() { this.gender = ['Male', 'Female', 'Others']; //Create a new user object this.user = new User({ email:"", password: { pwd: "" , confirm_pwd: ""}, gender: this.gender[0], terms: false}); } }
Для файла signup-form.component.html я собираюсь использовать тот же шаблон HTML, который обсуждался выше, но с небольшими изменениями. Форма регистрации имеет поле выбора со списком опций. Хотя это и работает, мы сделаем это Angular способом, пройдя по списку с помощью директивы ngFor
.
app/signup-form/signup-form.component.html
<div class="row custom-row"> <div class= "col-sm-5 custom-container jumbotron"> <form class="form-horizontal"> <fieldset> <legend>SignUp</legend> . . <!--- Gender Block --> <div class="form-group"> <label for="select">Gender</label> <select id="select"> <option *ngFor = "let g of gender" [value] = "g"> {{g}} </option> </select> </div> . . </fieldset> </form> </div> </div>
Затем мы хотим привязать данные формы к объекту пользовательского класса, чтобы при вводе регистрационных данных в форму был создан новый объект User, который временно сохраняет эти данные. Таким образом, вы можете синхронизировать представление с моделью, и это называется привязкой.
Есть несколько способов сделать это. Позвольте мне сначала познакомить вас с ngModel
и ngForm
.
ngForm и ngModel
ngForm и ngModel - это Angular директивы, которые необходимы для создания форм, управляемых шаблонами. Начнем с ngForm
. Вот выдержка о ngForm из Angular docs.
ДирективаNgForm
дополняет элементform
дополнительными функциями. Она содержит элементы управления, созданные с директивойngModel
и атрибутомname
, и отслеживает их свойства, в том числе их валидацию. Она также имеет свое собственное свойствоvalid
, которое истинно только в том случае, если все содержащиеся в нем элементы управления валидны.
Сначала обновите форму с помощью директивы ngForm
:
app/signup-form/signup-form.component.html
<form class="form-horizontal" #signupForm = "ngForm"> . . </form>
#signupForm
- это ссылочная переменная шаблона, которая ссылается на директиву ngForm
, которая управляет всей формой. В приведенном ниже примере демонстрируется использование ссылочного объекта ngForm
для валидации.
app/signup-form/signup-form.component.html
<button type="submit" class="btn btn-success" [disabled]="!signupForm.form.valid"> Submit </button>
Здесь signupForm.form.valid
вернет false, если все элементы формы не пройдут соответствующие проверки валидации. Кнопка отправки будет отключена до тех пор, пока форма не будет валидна.
Что касается привязки шаблона и модели, существует множество способов сделать это, и ngModel
имеет три разных синтаксиса для решения этой ситуации. Вот они:
- [(NgModel)]
- [NgModel]
- ngModel
Начнем с первого.
Двусторонняя привязка с использованием [(ngModel)]
[(ngModel)]
выполняет двустороннюю привязку для чтения и записи значений управления вводом. Если используется директива [(ngModel)]
, поле ввода берет начальное значение из класса связанных компонентов и обновляет его, всякий раз, когда обнаруживается какое-либо изменение входного значения управления (при нажатии клавиши и нажатии кнопки). Нижеприведенное изображение описывает процесс двустороннего связывания.

Вот код для поля ввода электронной почты:
<div class="form-group"> <label for="inputEmail">Email</label> <input type="text" [(ngModel)] = "user.email" id="inputEmail" name="email" placeholder="Email"> </div>
[(ngModel)] = "user.email"
связывает свойство электронной почты пользователя с входным значением. Я также добавил атрибут name и установил name = "email"
. Это важно, и вы получите сообщение об ошибке, если вы не объявили атрибут name при использовании ngModel.
Аналогичным образом добавьте к каждому элементу формы атрибут [(ngModel)]
и уникальный атрибут name. Теперь ваша форма должна выглядеть примерно так:
app/signup-form/signup-form.component.html
. . . <div ngModelGroup="password"> <div class="form-group" > <label for="inputPassword">Password</label> <input type="password" [(ngModel)] = "user.password.pwd" name="pwd" placeholder="Password"> </div> <div class="form-group"> <label for="confirmPassword" >Confirm Password</label> <input type="password" [(ngModel)] = "user.password.confirmPwd" name="confirmPwd" placeholder="Confirm Password"> </div> </div> <div class="form-group"> <label for="select">Gender</label> <select id="select" [(ngModel)] = "user.gender" name = "gender"> <option *ngFor = "let g of gender" [value] = "g"> {{g}} </option> </select> </div> . . .
ngModelGroup
используется для группировки одинаковых полей формы, чтобы мы могли запускать валидацию только по этим полям формы. Поскольку оба поля пароля связаны, мы помещаем их под одну группу ngModelGroup. Если все работает так, как ожидалось, свойство user
, связанное с компонентом, должно отвечать за хранение всех значений управления формой. Чтобы увидеть это в действии, добавьте следующее после тега формы:
{{user | json}}
Пропустите свойство user через JsonPipe
, чтобы отобразить модель как JSON в браузере. Это полезно для отладки и ведения логов. Вы должны увидеть JSON вывод следующим образом.

Значения текут от представления к модели. А как насчет другого пути? Попробуйте инициализировать объект пользователя некоторыми значениями.
app/signup-form/signup-form.component.ts
this.user = new User({ //initialized with some data email:"thisisfromthemodel@example.com", password: { pwd: "" , confirm_pwd: ""}, gender: this.gender[0] });
И они автоматически появляются в представлении:
{ "email": "thisisfromthemodel@example.com", "password": { "pwd": "", "confirm_pwd": "" }, "gender": "Male" }
Синтаксис двухсторонней привязки [(ngModel)]
позволяет легко создавать формы. Однако он имеет определенные недостатки; следовательно, существует альтернативный подход, в котором используется ngModel
или [ngModel]
.
Добавление ngModel в Mix
Когда используется ngModel
, мы фактически отвечаем за обновление свойства компонента с помощью значений управления вводом и наоборот. Входные данные автоматически не поступают в пользовательское свойство компонента.
Поэтому замените все экземпляры [(ngModel)] = ""
в ngModel
. Мы сохраним атрибут name
, потому что для всех трех версий ngModel нужен атрибут name
.
app/signup-form/signup-form.component.html
<div class="form-group"> <label for="inputEmail">Email</label> <input type="text" ngModel id="inputEmail" name="email" placeholder="Email"> </div>
С помощью ngModel
значение атрибута name станет ключом signupForm
ссылочного объекта ngForm
, который мы создали ранее. Так, например, signupForm.value.email сохранит контрольное значение для идентификатора электронной почты.
Замените {{user | json}}
на {{signupForm.value | json}}
, потому что сейчас все состояние сохраняется.
Односторонняя привязка с использованием [ngModel]
Что делать, если вам нужно установить начальное состояние из компонента связанного класса? Это то, что делает для вас [ngModel]
.

Здесь данные передаются от модели к виду. Внесите следующие изменения в синтаксис для использования односторонней привязки:
app/signup-form/signup-form.component.html
<div class="form-group"> <label for="inputEmail">Email</label> <input type="text" [ngModel] = "user.email" id="inputEmail" name="email" placeholder="Email"> </div>
Итак, какой подход вы должны выбрать? Если вы используете [(ngModel)]
и ngForm
вместе, у вас в конечном итоге будет два состояния для поддержки - user
и signupForm.value
- что может в итоге вас запутать.
{ "email": "thisisfromthemodel@example.com", "password": { "pwd": "thisispassword", "confirm_pwd": "thisispassword" }, "gender": "Male" } //user.value { "email": "thisisfromthemodel@example.com", "password": { "pwd": "thisispassword", "confirm_pwd": "thisispassword" }, "gender": "Male" } //signupForm.value
Следовательно, я рекомендую использовать метод односторонней привязки. Но решать вам.
Проверка и отображение сообщений об ошибках
Вот наши требования к валидации.
- Все элементы управления формой обязательны.
- Отключите кнопку отправки до тех пор, пока не будут заполнены все поля ввода.
- В поле электронной почты должно быть строго указан идентификатор электронной почты.
- Поле пароля должно иметь минимальную длину 8 символов.
- И пароль, и его подтверждение должны совпадать.

Сперва все просто. Вы должны добавить атрибут required
проверки для каждого элемента формы следующим образом:
app/signup-form/signup-form.component.html
<input type="text" [ngModel] = "user.email" name="email" #email = "ngModel" placeholder="Email" required>
Помимо атрибута required
, я также экспортировал новую ссылочную переменную шаблона #email
. Это делается для того, чтобы вы могли получить доступ к элементу управления Angular входного окна внутри самого шаблона. Мы будем использовать его для отображения ошибок и предупреждений. Теперь используйте свойство кнопки disabled, чтобы отключить кнопку:
app/signup-form/signup-form.component.html
<button type="submit" class="btn btn-success" [disabled]="!signupForm.form.valid"> Submit </button>
Чтобы добавить ограничение по электронной почте, используйте атрибут pattern, который работает с полями ввода. Шаблоны используются для указания регулярных выражений, подобных приведенным ниже:
pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$"
Для поля пароля все, что вам нужно сделать, это добавить атрибут minlength = ""
:
app/signup-form/signup-form.component.html
<input type="password" ngModel id="inputPassword" name="pwd" #pwd = "ngModel" placeholder="Password" minlength="8" required>
Чтобы отобразить ошибки, я собираюсь использовать условную директиву ngIf
для элемента div. Начнем с поля ввода для электронной почты:
app/signup-form/signup-form.component.html
<div class="form-group"> <label for="inputEmail">Email</label> <input type="text" [ngModel] = "user.email" name="email" #email = "ngModel" id="inputEmail" placeholder="Email" pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$" required> </div> <!-- This is the error section --> <div *ngIf="email.invalid && (email.dirty || email.touched)" class="alert alert-danger"> <div *ngIf = "email.errors?.required"> Email field can't be blank </div> <div *ngIf = "email.errors?.pattern && email.touched"> The email id doesn't seem right </div> </div>
Здесь много всего происходит. Начнем с первой строки раздела об ошибки.
<div *ngIf="email.invalid && (email.dirty || email.touched)" class="alert alert-danger">
Помните переменную #email
, которую мы экспортировали ранее? Она содержит некоторую информацию о состоянии управления вводом электронной почты. Это включает в себя: email.valid
, email.invalid
, email.dirty
, email.pristine
, email.touched
, email.untouched
и email.errors
. На приведенном ниже изображении подробно описывается каждое из этих свойств.

Таким образом, элемент div с *ngIf
будет отображаться только в том случае, если email невалидный. Тем не менее, пользователь будет встречен с ошибками о том, что поля ввода пустые, даже если у них есть возможность редактировать форму.
Чтобы избежать подобного сценария, мы добавили второе условие. Ошибка будет отображаться только после посещения элемента управления или изменения значения элемента управления.
Вложенные элементы div используются для покрытия всех случаев ошибок проверки. Мы используем email.errors
для проверки всех возможных ошибок валидации и последующего отображения их пользователю в виде собственных сообщений. Теперь следуйте той же процедуре для других элементов формы. Вот как я закодировал проверку паролей.
app/signup-form/signup-form.component.html
<div ngModelGroup="password" #userPassword="ngModelGroup" required > <div class="form-group"> <label for="inputPassword">Password</label> <input type="password" ngModel name="pwd" id="inputPassword" placeholder="Password" minlength ="8" required> </div> <div class="form-group"> <label for="confirmPassword" >Confirm Password</label> <input type="password" ngModel name="confirmPwd" id="confirmPassword" placeholder="Confirm Password"> </div> <div *ngIf="(userPassword.invalid|| userPassword.value?.pwd != userPassword.value?.confirmPwd) && (userPassword.touched)" class="alert alert-danger"> <div *ngIf = "userPassword.invalid; else nomatch"> Password needs to be more than 8 characters </div> <ng-template #nomatch > Passwords don't match </ng-template> </div> </div>
Все это начинает выглядеть немного грязно. Angular имеет ограниченный набор атрибутов валидатора: required
, minlength
, maxlength
и template
. Чтобы охватить любой другой сценарий, подобный сценарию сравнения паролей, вам придется полагаться на вложенные условия ngIf
, как я уже говорил выше. Или, в идеале, создать пользовательскую функцию валидатора, о которой я расскажу в третьей части этой серии.
В приведенном выше коде я использовал синтаксис ngIf else
, который был введен в последней версии Angular. Вот как это работает:
<div *ngIf="isValid;else notvalid"> Valid content... </div> <ng-template #notValid>Not valid content...</ng-template>
Отправить форму с помощью ngSubmit
Мы почти закончили форму. Теперь мы должны иметь возможность отправить форму, и контроль данных формы должен быть передан компонентному методу, например onFormSubmit()
.
app/signup-form/signup-form.component.ts
<form novalidate (ngSubmit)="onFormSubmit(signupForm)" #signupForm="ngForm"> ...
Теперь для компонента:
app/signup-form/signup-form.component.ts
... public onFormSubmit({ value, valid}: { value: User, valid: boolean }) { this.user = value; console.log( this.user); console.log("valid: " + valid); } ...
Финальная демонстрация
Вот окончательная версия приложения. Я добавил несколько классов bootstrap, чтобы сделать форму красивой.
Резюме
На этом мы заканчиваем. В этом уроке мы рассмотрели все, что вам нужно знать о создании формы в Angular, используя подход, основанный на шаблонах. Шаблонные формы популярны благодаря своей простоте и легком использовании.
Однако, если вам нужно создать форму с большим количеством элементов, этот подход станет весьма грязным. Итак, в следующем уроке мы рассмотрим модельный способ построения одной и той же формы.
Поделитесь своими мыслями в комментариях ниже.
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post