Книга: Angular и TypeScript. Сайтостроение для профессионалов
Назад: 6. Реализация коммуникации между компонентами
Дальше: 8. Взаимодействие с серверами с помощью HTTP и WebSockets

7. Работа с формами

В этой главе:

Angular Forms API (NgModel, FormControl, FormGroup, директивы форм, FormBuilder);

• работа с шаблон-ориентированными формами;

• работа с реактивными формами;

• валидация форм.

Angular обеспечивает поддержку огромного количества форм данных. Это выходит за рамки обычной привязки данных, поскольку поля форм считаются объектами первого класса, а над данными формы у вас есть полный контроль.

Данная глава начнется с демонстрации того, как можно реализовать пример формы регистрации пользователей на чистом HTML. Работая над этой формой, мы вкратце обсудим стандартные формы HTML и их недостатки. Затем покажем, какие возможности предлагает Forms API, и рассмотрим шаблон-ориентированный и реактивный подходы к созданию форм в Angular.

После изучения основ вы переработаете исходную версию регистрационной формы пользователя так, чтобы в ней использовался шаблон-ориентированный подход, и мы обсудим его плюсы и минусы. Затем сделаем то же самое с реактивным подходом. После этого обсудим валидацию форм. В конце главы вы примените новые знания при написании приложения онлайн-аукциона и начнете реализовывать один из его компонентов — форму поиска.

Шаблон-ориентированный подход против реактивного подхода

При шаблон-ориентированном подходе формы полностью запрограммированы в шаблоне компонента. Он определяет структуру формы, формат ее полей и правила валидации.

Напротив, используя реактивный подход, вы программно создаете модели форм в коде (в данном случае в коде TypeScript). Шаблон может быть либо определен статически и привязан к существующей модели форм, либо сгенерирован динамически, основываясь на модели.

К концу главы вы познакомитесь с Angular Forms API, с различными способами работы с формами, а также с выполнением валидации данных.

7.1. Обзор форм HTML

В HTML предоставляются основные функции для отображения форм, валидации введенных значений и отправки данных на сервер. Но формы HTML могут быть недостаточно хороши для реальных бизнес-приложений, для которых требуется способ программно обработать введенные данные, применить пользовательские правила валидации, отобразить удобные для пользователя сообщения об ошибках, преобразовать формат введенных данных и выбрать способ отправки данных на сервер. Для бизнес-приложений одним из самых важных факторов при выборе веб-фреймворка является то, насколько хорошо он работает с формами.

В этом разделе мы рассмотрим стандартную функциональность HTML для работы с формами на примере формы регистрации пользователей и определим список требований к современным веб-приложениям, выполнение которых позволит им соответствовать ожиданиям пользователей. Мы также обсудим функциональность для работы с формами, предоставляемую Angular.

7.1.1. Стандартная функциональность браузера

Вы, наверное, задаетесь вопросом, что еще нужно от фреймворка, помимо привязки данных, если HTML уже позволяет проверять и отправлять формы. Чтобы ответить на этот вопрос, взглянем на форму HTML, которая использует только стандартную функциональность браузера (листинг 7.1).

Листинг 7.1. Форма регистрации пользователя, написанная на простом HTML

<form action="/register" method="POST">

  <div>Username:         <input type="text"></div>

  <div>SSN:              <input type="text"></div>

  <div>Password:         <input type="password"></div>

  <div>Confirm password: <input type="password"></div>

  <button type="submit">Submit</button>

</form>

Эта форма содержит кнопку и четыре поля ввода: имя пользователя, номер социального страхования (Social Security Number, SSN), пароль и подтверждение пароля. Пользователи могут вводить абсолютно любые значения: вводимые данные здесь не проверяются. Когда пользователь нажимает кнопку Submit (Отправить), значения в форме отправляются в конечную точку сервера /register с помощью HTTP-запроса POST, после чего страница обновляется.

Поведение стандартных форм HTML не всегда подходит для одностраничных приложений, для которых чаще всего требуется следующая функциональность.

Правила валидации должны быть установлены для отдельных полей ввода.

• Сообщения об ошибке должны отображаться рядом с теми полями ввода, с которыми возникли проблемы.

• Зависимые поля нужно проверять одновременно. Данная форма имеет поля для ввода и подтверждения пароля, поэтому при изменении значения в одном из них нужно заново выполнить валидацию обоих полей.

• Приложение должно контролировать значения, которые отправляются на сервер. Когда пользователь нажимает кнопку Submit (Отправить), приложение должно вызвать функцию — обработчик событий, чтобы передать значения формы. Приложение может проверять значения или изменять их формат перед тем, как создать запрос на отправку.

• Приложение должно решить, как отправить данные на сервер: с помощью обычного HTTP-запроса, запроса Ajax или сообщения WebSocket.

Атрибуты валидации HTML и семантические типы входных данных частично удовлетворяют первым двум требованиям.

Атрибуты валидации HTML

Существует несколько стандартных атрибутов валидации, которые позволяют проверять отдельные поля ввода: required, pattern, maxlength, min, max, step и т.д. Например, можно указать, что поле username является обязательным и его значение должно содержать только буквы и цифры.

<input id="username" type="text" required pattern="[a-zA-Z0-9]+">

Здесь используется регулярное выражение [a-zA-Z0-9]+, чтобы ограничить диапазон вводимых значений для этого поля. Когда пользователь нажимает кнопку Submit (Отправить), форма будет проверена до того, как сформируется запрос на отправку. На рис. 7.1 показано стандартное сообщение об ошибке, отображенное в браузере Chrome, когда значения в поле username не соответствуют заданному шаблону.

73862.png 

Рис. 7.1. Сообщение об ошибке валидации

У этого сообщения есть несколько недостатков:

оно слишком расплывчатое и не помогает пользователю идентифицировать и решить проблему;

• как только поле ввода теряет фокус, сообщение об ошибке исчезает;

• такой формат сообщения не будет соответствовать другим стилям в приложении.

Приведенное поле ввода не позволяет пользователю вводить недопустимые значения, но при этом вы не можете сделать сайт более удобным, предоставив возможность проверять данные на стороне клиента.

Семантические типы входных данных

HTML поддерживает множество типов элементов ввода данных: text, number, url, email и т.д. Выбор правильного типа поля формы может помешать пользователю вводить недопустимые значения. И, хотя такой выбор делает сайт более удобным, этого все равно недостаточно для удовлетворения нужд вашего приложения, связанных с проверкой данных.

Рассмотрим поле для ввода ZIP-кода (почтовый индекс в США). Может возникнуть соблазн использовать элемент ввода number, так как ZIP-код представляет собой числовое значение (по крайней мере в Соединенных Штатах). Чтобы значения оставались в определенном диапазоне, можно задействовать атрибуты min и max. Например, для пятизначного ZIP-кода подойдет следующая разметка:

<input id="zipcode" type="number" min="10000" max="99999">

Но не каждое пятизначное число является действительным ZIP-кодом. В более сложном примере может понадобиться объявить допустимыми лишь некоторые ZIP-коды в зависимости от того, в каком штате находится пользователь.

Для решения реальных задач нужна более продвинутая поддержка форм, которую может предоставить фреймворк приложения. Посмотрим, что в данном случае предлагает Angular.

7.1.2. Forms API в Angular

Существует два подхода в работе с формами в Angular: шаблон-ориентированный и реактивный. Для каждого из них в Angular предоставляется отдельный API (набор директив и классов TypeScript).

В шаблон-ориентированном подходе модель формы определяется шаблоном компонента с помощью директив. Поскольку при определении модели формы вы ограничены синтаксисом HTML, этот подход годится только для простых сценариев.

Для сложных форм больше подходит реактивный подход. Используя его, вы создаете базовую структуру данных непосредственно в коде (а не в шаблоне). После создания модели связываете элементы шаблона HTML с моделью, задействуя специальные директивы с префиксом form*. В отличие от шаблон-ориентированных реактивные формы можно протестировать без участия браузера.

Выделим несколько важных концепций, чтобы в дальнейшем прояснить разницу между шаблон-ориентированными и реактивными формами.

В обоих типах форм есть модель, являющаяся базовой структурой данных, хранящей данные формы. В шаблон-ориентированном подходе Angular неявно создает модель на основе директив, которые вы прикрепляете к элементам шаблона. В реактивном подходе вы создаете модель явно и потом привязываете элементы шаблона HTML к данной модели.

• Модель не является произвольным объектом. Это объект, который был создан с использованием классов, определенных в модуле @angular/forms: FormControl, FormGroup и FormArray. В шаблон-ориентированном подходе вы не работаете с указанными классами напрямую, в то время как, используя реактивный подход, вы явным образом создаете экземпляры этих классов.

• Применение реактивного подхода не освобождает от написания шаблона HTML. Angular не будет генерировать представление за вас.

Включение поддержки Forms API

Оба типа форм — шаблон-ориентированные и реактивные — должны быть явно включены до того, как вы начнете их использовать. Чтобы включить шаблон-ориентированные формы, добавьте модуль FormsModule из @angular/forms в список imports директивы NgModule, которая задействует Forms API. Для реактивных форм применяйте модуль ReactiveFormsModule. Вот как это делается:

76645.png 

Мы не будем повторять данный код для каждого примера в данной главе — все они предполагают, что эти модули импортированы. Все загружаемые фрагменты кода для нашей книги импортируют модули в AppModule.

7.2. Шаблон-ориентированные формы

Как мы говорили ранее, чтобы определить модель в шаблон-ориентированном подходе, можно использовать только директивы. Но какие именно? Эти директивы поставляются с модулем FormsModule: NgModel, NgModelGroup и NgForm.

В главе 5 мы обсудили, каким образом можно применять директиву NgModel для двухсторонней привязки данных. Но в Forms API у нее совершенно другая роль: она отмечает элемент HTML, который должен стать частью модели формы. Несмотря на то, что эти две роли различны, они не конфликтуют друг с другом и могут быть безопасно использованы в одном элементе HTML. Примеры мы рассмотрим ниже в данном разделе. Кратко рассмотрим упомянутые директивы, а потом применим шаблон-ориентированный подход к нашей форме регистрации.

7.2.1. Обзор директив

Здесь мы дадим краткое описание трех основных директив из модуля FormsModule: NgModel, NgModelGroup и NgForm. Мы покажем, каким образом их можно использовать в шаблоне, и выделим их наиболее важные особенности.

NgForm

Это директива, представляющая собой форму целиком. Она автоматически прикрепляется к каждому элементу <form>. Косвенным образом создает экземпляр класса FormGroup, который представляет данную модель и хранит данные формы (подробнее о FormGroup вы узнаете далее в этой главе). Директива NgForm автоматически распознает все элементы-потомки HTML, отмеченные директивой NgModel, и добавляет их значения в модель формы.

Директива NgForm имеет несколько селекторов, которые можно использовать для того, чтобы прикрепить ее к другим элементам, а не только <form>:

73882.png 

Этот синтаксис пригодится в случае использования фреймворка CSS, который требует, чтобы у элементов HTML была определенная структура и элемент <form> не может быть задействован.

Если вы хотите, чтобы Angular исключил какой-то конкретный элемент <form> из обработки, то используйте атрибут ngNoForm:

<form ngNoForm></form>

Атрибут ngNoForm предотвращает создание экземпляра директивы NgForm и его прикрепление к элементу <form>.

Директива NgForm имеет свойство exportAs, объявленное в аннотации @Directive, позволяющее использовать значение этого свойства для создания локальной переменной шаблона, которая ссылается на экземпляр директивы NgForm:

<form #f="ngForm"></form>

<pre>{{ f.value | json }}</pre>

Сначала вы указываете ngForm в качестве значения свойства exportAs директивы NgForm; переменная f указывает на экземпляр директивы NgForm, прикрепленный к элементу <form>. Затем вы можете использовать переменную f для получения доступа к членам объекта директивы NgForm. Один из этих членов — value — представляет текущее значение всех полей формы в качестве объекта JavaScript. Вы можете пропустить его через стандартный канал json, чтобы отобразить значение формы на странице.

Директива NgForm перехватывает стандартное событие формы HTML submit и предотвращает автоматическую отправку формы. Вместо этого она генерирует пользовательское событие ngSubmit:

<form #f="ngForm" (ngSubmit)="onSubmit(f.value)"></form>

Данный код подписывается на событие ngSubmit, используя синтаксис привязки событий. Элемент onSubmit — произвольное имя метода, определенного в компоненте, вызываемого в тот момент, когда генерируется событие ngSubmit. Для передачи всех значений формы в качестве аргумента этого метода задействуйте переменную f, чтобы получить доступ к свойству value директивы NgForm.

NgModel

В контексте Forms API данная директива является отдельным полем на форме. Она неявно создает экземпляр класса FormControl, который представляет собой данную модель и хранит данные полей ввода (подробнее о FormControl вы узнаете далее в этой главе).

Вы прикрепляете объект FormControl к элементу HTML с помощью атрибута ngModel. Обратите внимание: Forms API не требует, чтобы атрибуту ngModel было присвоено какое-либо значение, а также заключения атрибута в скобки:

73891.png 

Свойство NgForm.value указывает на объект JavaScript, содержащий значения всех полей формы. Значение атрибута поля name становится именем свойства объекта JavaScript в NgForm.value.

Как и NgForm, директива NgModel имеет свойство exportAs, поэтому можно создавать переменную в шаблоне, которая будет ссылаться на экземпляр директивы NgModel и на ее свойство value:

73899.png 

NgModelGroup

Данная директива представляет собой часть формы и позволяет группировать ее поля. Подобно директиве NgForm, неявно создает экземпляр класса FormGroup. По сути, NgModelGroup создает вложенный объект в объекте, который хранится в свойстве NgForm.value. Все поля — потомки объекта класса NgModelGroup — становятся свойствами вложенного объекта.

Вот как можно это использовать:

73910.png 

7.2.2. Доработка формы HTML

Переработаем код примера формы регистрации пользователя, показанный в листинге 7.1. Выше была приведена форма, написанная на простом HTML, в которой не применялись какие-либо функции Angular. Теперь вы превратите ее в компонент Angular, добавите логику валидации и включите программную обработку события submit. Начнем с переработки кода шаблона, а потом перейдем к части, написанной на TypeScript. В первую очередь модифицируем элемент <form> (листинг 7.2).

Листинг 7.2. Форма с поддержкой Angular

<form #f="ngForm" (ngSubmit)="onSubmit(f.value)">

        <!-- Здесь будут располагаться поля форм -->

</form>

Вы объявляете локальную переменную f, которая указывает на объект директивы NgForm, прикрепленный к элементу <form>. Вам необходима эта переменная, чтобы получить доступ к таким свойствам формы, как value и valid, и проверить, имеет ли форма ошибки определенного типа.

Вы также настраиваете обработчик для события ngSubmit, которое генерируется директивой NgForm. Вы не хотите отслеживать стандартное событие submit, и NgForm перехватывает событие submit и останавливает его распространение. Так предотвращается автоматическая отправка формы на сервер, приводящая к перезагрузке страницы. Вместо этого директива NgForm генерирует свое собственное событие ngSubmit.

Метод onSubmit() является обработчиком событий. Он определяется как метод экземпляра компонента. В шаблон-ориентированных формах метод onSubmit() принимает один аргумент: значение формы, которая является простым объектом JavaScript, содержащим значения всех полей формы. Затем вы изменяете поля username и ssn (листинг 7.3).

Листинг 7.3. Модифицированные поля username и ssn

73921.png 

Теперь изменим поля для ввода пароля (листинг 7.4). Поскольку они связаны друг с другом и представляют одинаковые значения, вполне естественно объединить их в группу. Кроме того, будет удобно рассматривать оба пароля как один объект, когда вы будете реализовывать валидацию формы далее в этой главе.

Листинг 7.4. Модифицированные поля ввода паролей

73929.png 

Кнопка Submit (Отправить) остается единственным элементом HTML в шаблоне, она не изменилась по сравнению с кнопкой, использованной в HTML-версии формы:

<button type="submit">Submit</button>

Теперь, когда вы закончили перерабатывать код шаблона, обернем его в компонент. Ниже приведен код компонента (листинг 7.5).

Листинг 7.5. Компонент формы HTML

@Component({

  selector: 'app',

  template: `...`

})

class AppComponent {

  onSubmit(formValue: any) {

    console.log(formValue);

  }

}

Мы не включали содержимое шаблона, чтобы листинг оставался кратким, но здесь должна находиться его переработанная версия, описанная ранее.

78208.png 

Рис. 7.2. Директивы для форм на примере регистрационной формы

Обработчик событий onSubmit() принимает единственный аргумент — значение формы. Как вы могли заметить, обработчик не использует API, характерный для Angular. В зависимости от значения флага валидации вы можете решить, следует ли отправлять formValue на сервер. В этом примере вы выводите его в консоли.

На рис. 7.2 отображен наш образец регистрационной формы, к которой были применены директивы формы. Каждая директива обведена, чтобы вы могли видеть, из чего состоит форма. Полное рабочее приложение, иллюстрирующее, как использовать директивы форм, находится в файле 01_template-driven.ts в коде, поставляемом вместе с книгой.

7.3. Реактивные формы

В отличие от шаблон-ориентированного подхода процесс создания реактивной формы состоит из двух этапов. Сначала необходимо создать модель программно в коде, а затем привязать элементы HTML к данной модели с помощью директив в шаблоне. Начнем с первого этапа — создания модели.

7.3.1. Модель формы

Модель формы — базовая структура данных, которая содержит данные формы. Она создается из специальных классов, определенных в модуле @angular/forms: FormControl, FormGroup и FormArray.

FormControl

Класс FormControl — неделимый элемент формы. Обычно он соответствует одному элементу <input>, но может представлять собой и более сложный компонент пользовательского интерфейса, например календарь или слайдер. Объект класса FormControl содержит текущее значение соответствующего элемента HTML, информацию о статусе валидации элемента, а также сведения о том, был ли он изменен.

Вот как вы можете создать элемент управления:

73958.png 

FormGroup

Обычно представляет собой часть формы и является набором из нескольких FormControl. Собирает вместе значения и статусы каждого FormControl в группе. Если один из элементов управления в группе имеет недопустимые значения, то считается, что недопустимые значения имеет вся группа. Это удобно для управления связанными полями формы. Модель FormGroup также используется для представления формы целиком. Например, если диапазон дат представлен двумя полями ввода date, то они могут быть собраны в одну группу, чтобы получить диапазон дат как одно значение и отобразить ошибку, когда одна из введенных дат имеет недопустимое значение.

Вот как можно создать группу, которая объединяет элементы управления from и to:

let formModel = new FormGroup({

  from: new FormControl(),

  to :  new FormControl()

});

FormArray

Аналогична FormGroup, но имеет переменную длину. В отличие от FormGroup, которая представляет собой форму целиком или фиксированное подмножество полей формы, модель FormArray обычно является коллекцией полей, способной увеличиваться в размерах. Например, можно использовать FormArray для того, чтобы позволить пользователям вводить произвольное количество адресов электронной почты. Ниже приведена модель, которая может лечь в основу подобной формы:

73967.png 

7.3.2. Директивы форм

В реактивном подходе используется совершенно другой набор директив, отличный от шаблон-ориентированных форм. Директивы для реактивных форм поставляются вместе с модулем ReactiveFormsModule (см. раздел 7.2).

Имена всех реактивных директив начинаются со строки form*, поэтому можно легко отличить реактивную форму от шаблон-ориентированной, просто взглянув на шаблон. Реактивные директивы нельзя экспортировать; это значит, что вы не можете создать переменную в шаблоне, которая ссылается на экземпляр директивы. Так сделано специально, чтобы четко разделить два подхода. В шаблон-ориентированных формах вы не получаете доступ к классам модели, а в реактивных формах не можете работать с моделью в шаблоне.

В табл. 7.1 показано, каким образом классы модели соответствуют директивам формы. В первой колонке представлен список классов, которые мы рассмотрели в предыдущем разделе. Во второй даны директивы, привязывающие элемент DOM к экземпляру класса модели с помощью синтаксиса привязки свойств. Как вы можете заметить, FormArray не может использоваться с привязкой свойств. В третьей колонке представлен список директив, связывающих элемент DOM с классом модели, задействуя имя. Они могут применяться только в директиве formGroup.

Таблица 7.1. Соответствие классов модели директивам форм

Класс модели

Директивы форм

FormGroup

formGroup

formGroupName

FormControl

formControl

formControlName

FormArray

formArrayName

Рассмотрим директивы форм.

formGroup

Зачастую привязывает экземпляр объекта класса FormGroup, который представляет собой модель формы целиком, к элементу DOM высшего уровня вашей формы; обычно это элемент <form>. Все директивы, прикрепленные к элементам-потомкам DOM, будут находиться в области видимости formGroup и могут привязывать экземпляры моделей с помощью имени.

Чтобы использовать директиву formGroup, сначала создайте в компоненте объект класса FormGroup:

@Component(...)

class FormComponent {

  formModel: FormGroup = new FormGroup({});

}

Затем добавьте атрибут formGroup в элемент HTML. Значение атрибута formGroup ссылается на свойство компонента, которое содержит экземпляр класса FormGroup:

<form [formGroup]="formModel"></form>

formGroupName

Может использоваться в целях привязки вложенных групп в форме. Вам нужно, чтобы эта директива находилась в области видимости директивы-предка formGroup для привязки одного из его экземпляров-потомков FormGroup. Модель формы, которую можно применять с formGroupName, определяется следующим образом (листинг 7.6).

Листинг 7.6. Модель формы, которую можно использовать с formGroupName

73980.png 

Теперь рассмотрим шаблон (листинг 7.7).

Листинг 7.7. Шаблон formGroup

73988.png 

В области видимости formGroup можно использовать formGroupName, чтобы связать классы-потомки модели с помощью имен, определенных в объекте-предке класса FormGroup. Значение, присваиваемое атрибуту formGroupName, должно совпадать с именем, выбранным для объекта-потомка класса FormGroup в листинге 7.7 (в данном случае это dateRange).

Сокращенный синтаксис привязки свойств

Поскольку значение, которое вы присваиваете директиве *Name, является строковым литералом, можете использовать сокращенный синтаксис и убрать квадратные скобки, окружающие имя атрибута. Полная версия будет выглядеть следующим образом:

<div [formGroupName]="'dateRange'">...</div>

Обратите внимание на квадратные скобки, окружающие имя атрибута, и одинарные кавычки, окружающие значение атрибута.

formControlName

Должна использоваться в области видимости директивы formGroup. Привязывает один из экземпляров-потомков класса FormControl к элементу DOM.

Продолжим пример с диапазоном дат, который мы начали разрабатывать, ко­гда объясняли принцип действия директивы formGroupName. Компонент и модель формы остаются такими же. Вам нужно только завершить шаблон (листинг 7.8).

Листинг 7.8. Завершенный шаблон formGroup

<form [formGroup]="formModel">

  <div formGroupName="dateRange">

    <input type="date" formControlName="from">

    <input type="date" formControlName="to">

  </div>

</form>

Так же как и в случае использования директивы formGroupName, вы всего лишь указываете имя объекта класса FormControl, который хотите привязать к элементу DOM. Опять же эти имена вы выбрали при определении модели формы.

formControl

Можно использовать для форм с одним полем ввода в случае, когда вы не хотите создавать модель формы с помощью объекта класса FormGroup, но все еще хотите задействовать функции Forms API, такие как валидация и реактивное поведение, предоставленные свойством FormControl.valueChanges. Вы видели пример в главе 5, когда мы обсуждали наблюдаемые потоки. Вот суть того примера (листинг 7.9).

Листинг 7.9. Класс FormControl

73997.png 

Вы можете применять директиву ngModel, чтобы синхронизировать значение, введенное пользователем, со свойством компонента; но, поскольку вы задействуете Forms API, можете использовать его реактивные функции. В предыдущем примере вы применили несколько операторов RxJS для наблюдаемого потока, возвращенного свойством valueChanges, для удобства пользователя. Более подробную информацию об этом примере вы найдете в главе 5.

Рассмотрим шаблон компонента FormComponent из листинга 7.9:

<input type="text" [formControl]="weatherControl">

Ввиду того что вы работаете с самостоятельным объектом класса FormControl, который не является частью группы FormGroup, вы не можете использовать директиву formControlName для привязки его по имени. Вместо этого применяете formControl и синтаксис привязки свойств.

formArrayName

Должна использоваться в области видимости директивы formGroup. Привязывает один из экземпляров-потомков класса FormArray к элементу DOM. Ввиду того что элементы управления формы объекта класса FormArray не имеют имен, вы можете привязать их к элементам DOM только по индексу. Обычно вы отрисовываете их в цикле с помощью директивы ngFor.

Рассмотрим пример, в котором пользователям разрешено вводить произвольное количество адресов электронной почты (листинг 7.10). Здесь мы выделим только ключевые части кода, но вы можете найти полный рабочий вариант примера в файле 02_growable-items-form.ts в коде, поставляемом вместе с книгой. Сначала вы определяете модель.

Листинг 7.10. Файл 02_growable-items-form.ts: определение модели

74006.png 

В шаблоне поля для ввода адресов электронной почты отрисованы в цикле с помощью директивы ngFor (листинг 7.11).

Листинг 7.11. Файл 02_growable-items-form.ts: шаблон

74014.png 

Запись let i в цикле *ngFor позволяет автоматически привязывать значение индекса массива к переменной i, доступной в цикле. Директива formControlName привязывает объект класса FormControl в классе FormArray к элементу DOM; но вместо того, чтобы указать его имя, она использует переменную i, которая ссылается на индекс текущего элемента управления. Когда пользователи нажимают кнопку Add Email (Добавить адрес электронной почты), вы передаете новый экземпляр объекта класса FormControl в класс FormArray: this.formModel.get('emails') .push(new FormControl()).

107715.png 

Рис. 7.3. Форма, содержащая растущую коллекцию адресов электронной почты

 

На рис. 7.3 изображена форма с двумя полями ввода адресов электронной почты; анимированная версия, доступная на , показывает принцип работы. Каждый раз, когда пользователь нажимает кнопку Add Email (Добавить адрес электронной почты), новый экземпляр объекта класса FormControl помещается в объект класса FormArray, который содержит адреса электронной почты, и через привязку данных новое поле ввода отрисовывается на странице. Кроме того, благодаря привязке данных значение формы внизу обновляется в реальном времени.

7.3.3. Переработка формы-примера

Переработаем код образца — регистрационной формы пользователя — из листинга 7.1. Изначально это была форма, написанная на простом HTML, а потом вы применили шаблон-ориентированный подход. Теперь сделаем реактивную версию. Начнем с определения модели формы (листинг 7.12).

Листинг 7.12. Определение модели формы

74039.png 

Свойство formModel содержит экземпляр класса FormGroup, определяющий структуру формы. Вы будете использовать это свойство в шаблоне, чтобы связать модель с элементом DOM с помощью директивы formGroup. Оно инициализируется программно в конструкторе путем создания экземпляров классов модели. Имена, которые вы даете элементам управления формы в объектах-предках класса FormGroup, применяются в шаблоне для привязки модели к элементу DOM с помощью директив formControlName и formGroupName.

Группа passwordsGroup является вложенным объектом класса FormGroup, который объединяет поля ввода и подтверждения паролей. Будет удобно управлять их значениями как одним объектом, когда вы добавите валидацию данных формы.

Ввиду того что директивы реактивных форм нельзя экспортировать, вы не можете получить к ним доступ в шаблоне и передать их напрямую в метод onSubmit() в качестве аргумента. Вместо этого вы получаете доступ к значению путем использования свойства компонента, которое хранит модель формы.

Теперь, когда мы определили модель, можете написать разметку HTML, привязываемую к модели (листинг 7.13).

Листинг 7.13. Привязка HTML к модели

74046.png 

Поведение этой реактивной версии регистрационной формы идентично поведению шаблон-ориентированной формы, однако они отличаются внутренней реализацией. Полное приложение, иллюстрирующее, как создавать реактивные формы, находится в файле 03_reactive.ts в коде, который поставляется вместе с книгой.

7.3.4. Использование FormBuilder

FormBuilder упрощает создание реактивных форм. Он не предоставляет никакой уникальной функциональности по сравнению с прямым использованием классов FormControl, FormGroup и FormArray, но его API более краткий, что избавляет от необходимости постоянно вводить имена классов.

Переработаем класс компонента из предыдущего раздела так, чтобы в нем применялся класс FormBuilder. Шаблон останется точно таким же, но вы измените способ создания formModel. Вот как он должен выглядеть (листинг 7.14).

Листинг 7.14. Переработка formModel с использованием класса FormBuilder

74058.png 

В отличие от класса FormGroup класс FormBuilder позволяет создавать объекты класса FormControl с помощью массива. Каждый элемент массива имеет особое значение. Первый элемент — изначальное значение объекта класса FormControl. Второй — функция-валидатор. Он также принимает третий аргумент, который является асинхронной функцией-валидатором. Остальными элементами массива можно пренебречь.

Как вы можете заметить, конфигурирование модели формы с помощью FormBuilder выглядит менее объемно и основывается на объектах конфигурации, а не на явном создании объектов классов элементов управления. Полное приложение, иллюстрирующее использование класса FormBuilder, находится в файле 04_form в коде, который поставляется вместе с книгой.

7.4. Валидация данных формы

Одним из преимуществ использования Forms API, по сравнению с обычной привязкой данных, является тот факт, что вы можете провести валидацию данных формы. Валидация доступна для обоих типов форм: шаблон-ориентированной и реактивной. Вы создаете валидаторы как простые функции TypeScript. В реактивном подходе вы применяете функции напрямую, в шаблон-ориентированном подходе оборачиваете их в пользовательские директивы.

Начнем с валидации реактивных форм, а потом перейдем к шаблон-ориентированным. Мы рассмотрим основы и применим валидацию к нашему примеру — регистрационной форме.

7.4.1. Валидация реактивных форм

Валидаторы — обычные функции, которые имеют следующий интерфейс:

74070.png 

Функция валидатора должна объявлять один параметр типа AbstractControl и возвращать объектный литерал. Реализация функции не ограничена — все зависит только от автора валидатора. Параметр AbstractControl является суперклассом для классов FormControl, FormGroup и FormArray, и это значит, что валидаторы могут быть созданы для всех классов модели.

Несколько заранее определенных валидаторов поставляется с Angular: required, minLength, maxLength и pattern. Они определяются как статические методы класса Validators, объявленного в модуле @angular/forms, и соответствуют стандартным атрибутам валидации HTML5.

Как только у вас появится валидатор, нужно будет настроить модель для его использования. В реактивном подходе вы предоставляете валидаторы в качестве аргументов для конструкторов классов модели. Рассмотрим пример:

74077.png 

Вы также можете предоставить список валидаторов в качестве второго аргумента:

let usernameControl = new FormControl('', [Validators.required,

  Validators.minLength(5)]);

Чтобы проверить корректность данных элемента управления, используйте свойство valid, которое возвращает либо true, либо false:

74087.png 

Если какое-либо правило проверки не пройдено, то вы можете получить объекты ошибок, сгенерированные функциями валидатора.

let errors: {[key: string]: any} = usernameControl.errors;

Объект ошибки

Ошибка, возвращенная валидатором, представлена объектом JavaScript, который имеет свойство с таким же именем, как и у валидатора. Является ли он объектным литералом или объектом со сложной прототипной цепочкой, для валидатора не имеет значения.

Значение свойства может иметь любой тип и предоставлять дополнительную информацию об ошибке. Например, стандартный валидатор Validators.minLength() возвращает следующий объект ошибки:

{

  minlength: {

    requiredLength: 7,

    actualLength: 5

  }

}

Объект имеет свойство высшего уровня, имя которого соответствует имени валидатора: minlength. Его значение также является объектом с двумя полями: requiredLength и actualLength. Эти детали могут использоваться, чтобы отображать удобное для пользователя сообщение об ошибке.

Не все валидаторы предоставляют дополнительную информацию об ошибке. Иногда свойство высшего уровня лишь указывает, что произошла ошибка. В этом случае свойство инициализируется со значением true. Ниже приведен пример стандартного объекта ошибки Validators.required():

{

  required: true

}

Пользовательские валидаторы

Стандартные валидаторы хороши для валидации основных типов данных, таких как строки и числа. Если необходимо проверять более сложные типы данных или логику приложения, может понадобиться создать пользовательский валидатор. Ввиду того что валидаторы в Angular — всего лишь функции с определенной сигнатурой, их довольно просто создать. Нужно объявить функцию, принимающую экземпляр одного из типов элементов управления — FormControl, FormGroup или FormArray — и возвращающую объект, который представляет собой ошибку валидации (см. вставку «Объект ошибки»).

Ниже следует пример пользовательского валидатора, проверяющего, является ли значение, введенное в элементе управления, корректным номером социального страхования (Social Security Number, SSN), представляющим собой уникальный идентификатор для каждого гражданина Соединенных Штатов:

74094.png 

Пользовательские валидаторы используются таким же образом, как и стандартные:

let ssnControl = new FormControl('', ssnValidator);

Полное работающее приложение, иллюстрирующее, как создавать пользовательские валидаторы, находится в файле 05_custom-validator.ts в коде, который поставляется вместе с книгой.

Валидаторы для групп

Вам может понадобиться проверять значения не только отдельных полей, но и их групп. Angular также позволяет определять функции-валидаторы для объектов класса FormGroup.

Создадим валидатор, который гарантирует, что поля password и password-confirmation из нашего примера формы регистрации имеют одинаковые значения. Одна из возможных реализаций выглядит следующим образом:

74101.png 

Сигнатура функции соответствует интерфейсу ValidatorFn: первый параметр имеет тип FormGroup, который является подклассом AbstractControl, а ее возвращаемый тип — объектный литерал. Обратите внимание на то, что вы используете свойство ECMAScript, называемое деструктурированием (см. одноименный подраздел в приложении А). Вы извлекаете свойство value из экземпляра класса FormGroup, которое будет передано в качестве аргумента. Здесь это имеет смысл, поскольку вы никогда не получаете доступ к какому-либо другому свойству объекта класса FormGroup в коде валидатора.

Далее вы получаете имена всех свойств в объекте value и сохраняете их в двух переменных, first и rest. Переменная first — это имя свойства, которое будет использовано в качестве ссылочного значения; значения всех других свойств должны быть одинаковыми, чтобы пройти проверку. В переменной rest хранятся имена всех других свойств. Вы снова применяете функциональность деструктурирования для извлечения ссылок на элементы массива (см. подраздел А.5.3 «Деструктурирование» в приложении А). Наконец, вы возвращаете либо значение null, если значения группы являются допустимыми, либо в противном случае объект, который указывает на ошибку.

Выполнение валидации для примера формы регистрации

Теперь, когда мы рассмотрели основы, добавим валидацию в наш пример. Вы будете использовать валидаторы ssnValidator и equalValidator, которые мы реализовали ранее в этом разделе. Далее представлена модифицированная модель формы (листинг 7.15).

Листинг 7.15. Модифицированная модель формы

74110.png 

Прежде чем выводить модель формы в консоли с помощью метода onSubmit(), вы проверяете, является ли форма действительной:

onSubmit() {

  if (this.formModel.valid) {

    console.log(this.formModel.value);

  }

}

В подходе, ориентированном на работу с моделями, для конфигурирования валидаторов требуется внести изменения только в код, но вам также нужно сделать несколько изменений в шаблоне. Следует отобразить ошибки валидации, когда пользователь вводит недопустимое значение. Рассмотрим модифицированную версию шаблона (листинг 7.16).

Листинг 7.16. Измененный шаблон

74118.png 

Обратите внимание на то, как вы получаете доступ к методу hasError(), доступному в модели формы, когда при определенных условиях показываете сообщения об ошибке. Он принимает два параметра: имя ошибки валидации, наличие которой вы хотите проверить, и путь к необходимому полю в модели формы. В случае поля username оно является прямым потомком объекта класса FormGroup высшего уровня, который представляет собой модель формы, так что вы просто указываете имя элемента управления. Но поле password — потомок вложенного объекта класса FormGroup, поэтому путь к элементу управления указывается как массив строк. Первый элемент — имя вложенной группы, а второй — собственно имя поля password. Как и поле username, группа passwordsGroup указывает путь как строку ввиду того, что она является прямым потомком объекта высшего уровня класса FormGroup.

Полное работающее приложение, иллюстрирующее, как использовать функции валидатора с реактивными формами, находится в файле 09_reactive-with-validation.ts в коде, поставляемом вместе с книгой. В этом примере вы жестко закодировали сообщения об ошибках в шаблоне, но их можно предоставить, применяя валидаторы. Чтобы увидеть пример, в котором сообщения об ошибках предоставляются динамически, см. файл 07_custom-validator-error-message.ts.

Конфигурирование валидаторов с помощью FormBuilder

Валидаторы также можно сконфигурировать, когда вы задействуете FormBuilder для определения моделей форм. Ниже приведена модифицированная версия модели нашего примера — регистрационной формы — с использованием FormBuilder:

76739.png 

Асинхронные валидаторы

Forms API поддерживает асинхронные валидаторы. Они могут использоваться для проверки значений форм с помощью удаленного сервера, что включает в себя отправку HTTP-запроса. Как и обычные, асинхронные валидаторы являются функциями. Единственное отличие состоит в том, что асинхронные валидаторы должны возвращать объекты типов Observable или Promise. Ниже приведен асинхронный вариант валидатора для номера SSN (листинг 7.17).

Листинг 7.17. Асинхронный валидатор для номера SSN

74136.png 

Асинхронные валидаторы передаются в качестве третьего аргумента в конструкторы классов модели:

let ssnControl = new FormControl('', null, asyncSsnValidator);

Полное работающее приложение, иллюстрирующее, как использовать асинхронные валидаторы, находится в файле 08_async-validator.ts в коде, который поставляется вместе с книгой.

Проверка состояния и допустимости значения поля

Вы уже знакомы с такими свойствами элементов управления, как valid, invalid и errors, используемыми для проверки состояния поля. Здесь мы рассмотрим некоторые другие свойства, предназначенные для повышения удобства применения.

Затронутые и незатронутые поля. В дополнение к проверке допустимости значений элементов управления вы также можете использовать свойства touched и untouched, чтобы проверить, работал ли пользователь с данным полем. Если пользователь помещает поле в фокус с помощью клавиатуры или мыши, то оно считается затронутым; в противном случае — незатронутым. Это может быть полезно при отображении сообщений об ошибках: если поле имеет недопустимое значение, но пользователь с ним не работал, то вы можете выбрать не выделять его красным цветом, поскольку это не ошибка пользователя. Рассмотрим следующий пример:

74143.png 

примечание

Все свойства, рассмотренные в этом примере, доступны в классах модели FormControl, FormGroup и FormArray, а также в шаблон-ориентированных директивах NgModel, NgModelGroup и NgForm.

Обратите внимание на пример привязки класса CSS в последней строке. При определенных условиях класс CSS hasError применяется для элемента, если выражение справа имеет значение true. При использовании только свойства c.invalid граница поля будет выделена в процессе отрисовки страницы; но такое действие может смутить пользователей, особенно если на странице много полей ввода. Вместо этого вы добавляете еще одно условие: поле должно быть затронутым. Теперь оно выделяется только после того, как пользователь поработает с ним.

• Чистые и грязные поля. Еще одна пара полезных свойств: pristine и его противоположность dirty. Свойство dirty указывает на то, что поле было изменено после инициализации. Эти свойства могут быть использованы для напоминания пользователю о необходимости сохранить данные, прежде чем он покинет страницу или закроет диалоговое окно.

примечание

Все вышеупомянутые свойства имеют соответствующие классы CSS (ng-touched и ng-untouched, ng-dirty и ng-pristine, ng-valid и ng-invalid), которые автоматически добавляются к элементам HTML, когда свойство имеет значение true. Это может быть полезно, если нужно задать отдельный стиль для элементов, находящихся в особом состоянии.

• Ожидающие поля. Если для элемента управления определены асинхронные валидаторы, вам также может пригодиться булево свойство pending. Оно показывает, что допустимость значения в данный момент не определена. Это случается, когда асинхронный валидатор еще работает и нужно ждать результат. Данное свойство можно использовать для отображения индикатора прогресса.

Для реактивных форм свойство statusChanges типа Observable может быть более удобным. Оно отправляет одно из трех значений: VALID, INVALID и PENDING.

Валидация шаблон-ориентированных форм

Директивы — все, что можно использовать при создании шаблон-ориентированных форм, поэтому можно обернуть функции-валидаторы в директивы, чтобы применять их в шаблоне. Создадим директиву, которая оборачивает реализованный в листинге 7.17 валидатор SSN (листинг 7.18).

Листинг 7.18. Директива SsnValidatorDirective

74156.png 

Квадратные скобки, окружающие селектор ssn, указывают на то, что директива может быть использована в качестве атрибута. Это удобно, поскольку можно добавить атрибут к любому элементу <input> или к компоненту Angular, представленному как пользовательский элемент HTML.

В этом примере вы регистрируете функцию-валидатор с помощью заранее определенного токена Angular NG_VALIDATORS. Данный токен, в свою очередь, внедряется директивой NgModel, и та получает список всех валидаторов, прикрепленных к элементу HTML.

Далее NgModel передает экземпляру класса FormControl валидаторы, которые создаются ею неявно. Этот же механизм отвечает за запуск валидаторов; директивы — всего лишь альтернативный способ их конфигурирования. Свойство multi позволяет связывать множество значений с одним токеном. Когда токен внедрен в директиву NgModel, она получает список значений вместо одного. Это позволяет передавать несколько валидаторов. Вот как можно использовать директиву SsnValidatorDirective:

<input type="text" name="my-ssn" ngModel ssn>

Вы можете найти полное работающее приложение, в котором демонстрируется использование валидаторов-директив, в файле 06_custom-validator-directive.ts в коде, поставляемом вместе с книгой.

7.4.2. Выполнение валидации для примера формы регистрации

Теперь вы можете добавить валидацию формы для нашего примера. Начнем с шаблона (листинг 7.19).

Листинг 7.19. Шаблон валидации для формы регистрации

74167.png 

При шаблон-ориентированном подходе у вас нет модели в компоненте. Только шаблон может проинформировать обработчик формы о том, что она имеет допустимые значения, и поэтому вы передаете значение формы и состояние ее корректности как аргументы в метод onSumbit(). Вы также добавляете атрибут novalidate, чтобы браузер не выполнял собственную валидацию, мешая Angular совершать свою.

Директивы-валидаторы добавляются как атрибуты. Необходимая директива предоставляется Angular и становится доступна, когда вы зарегистрируете поддержку Forms API для объекта класса FormModule. Аналогично вы можете использовать директиву minlength для валидации поля password.

Чтобы ситуативно показывать и скрывать сообщения об ошибках валидации, вы используете тот же метод hasError(), применяемый в реактивной версии. Но для получения доступа к этому методу нужно задействовать свойство формы типа FormGorup, доступное в переменной f, которая ссылается на экземпляр директивы formGroup.

В методе onSubmit() вы проверяете корректность формы до того, как выведете значение в консоли (листинг 7.20).

Листинг 7.20. Проверка валидации формы

74175.png 

Остался последний шаг: нужно добавить пользовательский валидатор в список объявлений директивы NgModule, где вы определили компонент AppComponent (листинг 7.21).

Листинг 7.21. Добавление валидаторов-директив

74184.png 

Полное работающее приложение, иллюстрирующее, как использовать валидаторы-директивы для шаблон-ориентированных форм, находится в файле 10_template-driven-with-validation.ts в коде, который поставляется вместе с книгой.

7.5. Практикум: добавление валидации в форму поиска

Это упражнение начнется с того места, где мы остановились в главе 6. Вам нужно изменить код компонента SearchComponent так, чтобы включить валидацию формы и собрать данные, введенные в нее.

Когда данные с формы отправляются, вы выводите на экран значение формы в консоли браузера. Глава 8 посвящена общению с сервером, в ней вы переработаете код так, чтобы форма поиска выполняла реальный HTTP-запрос.

ch07_04.tif 

Рис. 7.4. Форма поиска с валидаторами

В этом разделе вы сделаете следующие шаги.

1. Добавите новый метод в класс ProductService, который возвращает массив всех доступных категорий продуктов.

2. Создадите модель, представляющую форму поиска, с помощью FormBuilder.

3. Сконфигурируете правила валидации для модели.

4. Переработаете шаблон так, чтобы в нем выполнялась привязка свойств для модели, созданной на предыдущем шаге.

5. Реализуете метод onSearch() для обработки события формы submit.

На рис. 7.4 показано, как будет выглядеть форма поиска после завершения упражнения. Отображены валидаторы в действии.

Если вы хотели бы увидеть финальную версию этого проекта, то откройте исходный код в каталоге auction для главы 7. В противном случае скопируйте каталог auction в другое место и следуйте инструкциям из данного раздела.

7.5.1. Изменение корневого модуля для добавления поддержки Forms API

Обновите файл app.module.ts, чтобы включить поддержку реактивных форм для вашего приложения (листинг 7.22). Импортируйте модуль ReactiveFormsModule из @angular/forms и добавьте его в список импортированных модулей в основном приложении NgModule.

Листинг 7.22. Обновленный файл app.module.ts

import { ReactiveFormsModule } from '@angular/forms';

@NgModule({

  imports: [

    BrowserModule,

    FormsModule,

    ReactiveFormsModule,

    RouterModule.forRoot([ ... ])

  ],

7.5.2. Добавление списка категорий в компонент SearchComponent

Каждый продукт имеет свойство categories, представленное массивом строк; один продукт может относиться к нескольким категориям. Форма должна позволять пользователю выбирать категорию во время поиска продуктов; нужен способ предоставить список всех доступных категорий форме, чтобы она могла отобразить их для пользователей.

В реальных приложениях категории, скорее всего, будут приходить с сервера. В этом примере онлайн-аукциона вы добавите метод в класс ProductService, который вернет жестко закодированные категории.

1. Откройте файл app/services/product-service.ts и добавьте метод getAllCa­tegories(), который не принимает никаких параметров и возвращает список строк:

getAllCategories(): string[] {

  return ['Books', 'Electronics', 'Hardware'];

}

2. Откройте файл app/components/search/search.ts и добавьте оператор импорта для ProductService:

import {ProductService} from '../../services/product-service';

3. Сконфигурируйте этот сервис как поставщик для компонента SearchCom­ponent:

@Component({

  selector: 'auction-search',

  providers: [ProductService],

  //...

})

4. Объявите свойство класса categories: string[] как ссылку на список категорий. Вы будете использовать ее для привязки данных:

export default class SearchComponent {

  categories: string[];

}

5. Объявите метод constructor с одним параметром: ProductService. Angular внедрит его при создании объекта компонента. Инициализируйте свойство categories, используя метод getAllCategories():

74203.png 

7.5.3. Создание модели формы

Теперь определим модель, которая будет обрабатывать форму поиска.

1. Откройте файл app/components/search/search.ts и добавьте оператор импорта для Forms API. Оператор import в начале файла должен выглядеть так:

import {Component} from '@angular/core';

import {FormControl, FormGroup, FormBuilder, Validators} from

  '@angular/forms';

2. Объявите свойство класса formModel для типа FormGroup:

export default class SearchComponent {

  formModel: FormGroup;

  //...

}

3. В конструкторе определите formModel с помощью класса FormBuilder:

const fb = new FormBuilder();

this.formModel = fb.group({

  'title': [null, Validators.minLength(3)],

  'price': [null, positiveNumberValidator],

  'category': [-1]

})

4. Добавьте функцию positiveNumberValidator:

function positiveNumberValidator(control: FormControl): any {

  if (!control.value) return null;

  const price = parseInt(control.value);

  return price === null || typeof price === 'number' && price > 0

      ? null : {positivenumber: true};

}

Функция positiveNumberValidator() пробует проанализировать целочислен­ное значение, полученное от FormControl, с помощью стандартной функции parseInt(). Если проанализированное значение представляет собой поло­жительное целое число, то функция вернет значение null, что означает отсутствие ошибок. В противном случае она вернет объект ошибки.

7.5.4. Переработка шаблона

Добавим в шаблон директивы форм, чтобы привязать модель, определенную в предыдущем шаге, к элементам HTML.

1. Вы определили модель формы с помощью реактивного подхода, поэтому в шаблоне должны прикрепить директиву NgFormModel к элементу <form>.

74210.png 

2. Определите правила валидации и при определенных условиях отображайте сообщения об ошибках для поля title:

<div class="form-group"

     [class.has-error]="formModel.hasError('minlength', 'title')">

  <label for="title">Product title:</label>

  <input id="title"

         placeholder="Title"

         class="form-control"

         type="text"

         formControlName="title"

         minlength="3">

  <span class="help-block"

        [class.hidden]="!formModel.hasError('minlength', 'title')">

    Type at least 3 characters

  </span>

</div>

Здесь вы используете классы CSS form-group, form-control, has-error и help-block, определенные в библиотеке Twitter Bootstrap. Они необходимы для того, чтобы соответствующим образом отрисовать форму и выделить поле красной границей в случае ошибки валидации. Более подробную информацию об этих классах вы можете найти в документации к Bootstrap в разделе Forms: .

3. Сделайте то же самое для поля с ценой продукта:

<div class="form-group"

      [class.has-error]="formModel.hasError('positivenumber', 'price')">

  <label for="price">Product price:</label>

  <input id="price"

          placeholder="Price"

          class="form-control"

          type="number"

          step="any"

          min="0"

          formControlName="price">

  <span class="help-block"

        [class.hidden]="!formModel.hasError('positivenumber', 'price')">

    Price is not a positive number

  </span>

</div>

4. Добавьте правила валидации и сообщение об ошибке для поля категории продукта:

<div class="form-group">

  <label for="category">Product category:</label>

  <select id="category"

          class="form-control"

          formControlName="category">

    <option value="-1">All categories</option>

    <option *ngFor="let c of categories"

            [value]="c">{{c}}</option>

  </select>

</div>

Кнопка Submit (Отправить) не изменяется.

7.5.5. Реализация метода onSearch()

Добавьте следующий метод onSearch():

onSearch() {

  if (this.formModel.valid) {

    console.log(this.formModel.value);

  }

}

7.5.6. Запуск онлайн-аукциона

Чтобы запустить приложение, откройте командную строку и запустите HTTP-сервер в корневом каталоге проекта. Введите в браузере — вы должны увидеть главную страницу, которая содержит форму поиска, показанную на рис. 7.4. В этой версии приложения показывается создание формы и валидация без выполнения поиска. Вы реализуете функциональность поиска в главе 8, когда мы будем рассматривать способы общения с сервером.

7.6. Резюме

Из этой главы вы узнали, как работать с формами в Angular. Вот основные выводы.

Существует два подхода к работе с формами: шаблон-ориентированный и реактивный. Первый проще, и его легче конфигурировать, но второй легче тестировать, он более гибок и предоставляет возможность управления формой.

• Реактивный подход более полезен для приложений, которые применяют не только отрисовщик DOM, но и какой-нибудь другой (например, NativeScript), нацеливаясь на небраузерные среды. Реактивные формы достаточно написать всего раз, после чего их можно использовать повторно для более чем одного отрисовщика.

• Какое-то количество стандартных валидаторов поставляется с Angular, но можно создавать собственные. Вы должны выполнять валидацию данных, введенных пользователем, но валидация на стороне клиента не может заменить валидацию на сервере. Считайте валидацию на стороне клиента способом предоставить пользователю мгновенную обратную связь с минимизацией количества запросов на сервер, содержащих недопустимые данные.

Назад: 6. Реализация коммуникации между компонентами
Дальше: 8. Взаимодействие с серверами с помощью HTTP и WebSockets

32
32
Alex
32