Перевод: Introduction to Angular 2 Forms — Template Driven vs Model Driven or Reactive Forms

В этой статье рассмотрим как работать с Angular 2 Forms API и как оно может быть полезно в построение сложный форм. Мы пройдемся по следующим темам:

  • Что такое Angular 2 Forms
  • Шаблон-ориентированные формы или путь Angular 1
  • Модель-ориентированные формы или новый функциональный подход
  • Обновление значенией в формах и как сбросить форму
  • Преимущества и недостатки форм обоих типов
  • Можно ли и должен ли я использовать эти подходы вместе ?
  • Какой подход выбрать по умолчанию ?

Что такое формы в Angular 2?

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

Каждое приложение интенсивно использующее формы должно обеспечить ответы на следующие вопросы:

  • как отслеживать глобальное состояние формы
  • знать какая часть формы валидная и какая все еще невалидна
  • верно отображать сообщения о ошибках что бы пользователь мог знать что от него требуется что бы исправить ошибку

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

Angular 2 фреймворк предоставляет пару стратегий создания форм. И мы начнем с подхода который наиболее близок к Angular 1.

Angular 2 шаблон-ориентированные формы

Angular 1 работает с формами через знаменитую директиву ng-model (прочитать об этом можно здесь).

Мгновенное дву-направленное связывание ng-model в Angular 1 действительно очень удобна поскольку позволяет быстро синхронизировать данные формы с моделью.

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

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

Обратите внимание что NgModel включает точно такую же функциональность как и Angular 1.

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

В отличие от Angular 1, ngModel и другие директивы связанные с формами не доступны по умолчанию, нам нужно импортировать их в наше приложение:

В данном примере мы добавили шаблон-ориентированные формы импортируя FormsModule в наше приложение.

Это подходящий пример для тестовой версии, для версии идущей в продакшин вам стоит взглянуть на следующую статью @NgModule для загрузки модулей.

Далее давайте создадим нашу первую Angular 2 форму.

Первая шаблон-ориентированная форма

Взгляните на код создания формы:

 

В данном коде мы объявили простую форму с двумя полями (controls): first name и password, оба поля обязательны (помечаются посредством атрибута required).

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

Валидация NgModel

Обратите внимание мы использовали [(ngModel)], такой код означает создание дву-направленной связи поля формы и модели данных, с именем user.

Этот синтаксис [(ngModel)] так же называется ‘Box of Bananas’ синтаксис 🙂

Далее если пользователь кликнет требуемые поля отобразится

Angular отслежывает три состояния формы (которое используется для выбора соотвествующего CSS стиля):

  • touched (был клик) или untouched (кликов не было)
  • valid (поле верное) или invalid (поле неверное)
  • pristine (изменений не были) или dirty (были изменения в форме))

Эти классы очень полезны для отображения ошибок формы.

Так же Angular отслеживает валидное состояние всей формы целиком позволяя нам разрешать/запрещать кнопку отправки формы. Это функциональность общая для шаблон-ориентированных и модель-ориентированных форм.

Вся логика работы должна же быть в контроллере формы?

Давай взглянем на контреллер данной формы:

Не так уж и много! Здесь мы только объявили объект user, и обработчик отправки формы ngSubmit.

Вся же локика отслеживания ошибок и вализации будет работать по умолчанию без нашего вмешательства!

Как же Angular справляется со всем этим?

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

Если по какой то причине вам этого не нужно то с помощью атрибута ngNoForm можно отключит эту функциональность.

Более того к кождому input будет привязана директива которая зарегистрироуется в FormGroup как ControlGroup, а так же валидаторы если будут использованы атрибуты required и maxlength.

Наличие [(ngModel)] так же создаст дву-направленую связь между формой и моделью данных.

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

Что если мы не хотим дву-направленую связь, что если нам нужно только инициализация поля?

Иногда нам будет нужно только создание формы и ее инициализация. В этом случае нам нужно только что бы пользователь заполнял поля в форме и отправил эти данные на сервер. Мы сможем это сделать используя простой синтаксис [ngModel]:

Это позволит нам проинициализировать форму с объектом user:

Что если нам не нужна инициализация полей, но все еще нужна валидация?

Для примера создадим форму без инициализации полей, но с валидацией:

 

Преимущества и недостатки шаблонно-ориентированных форм

Основное достоинство этого способа это простота использования.

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

Другим недостатком этого способа, это невозможность тестирования формы без использования библиотек фунционального тестирования таких как PhantomJs.

Шаблон-ориентированные формы с точки зрения функционального программирования

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

Каждая форма имеет свое состояние которое обновляется при каждом взаимодействие с формой и программист должен управлять этим состоянием предотвращая его неверного поведения. Это может стать достаточной сложной задачей при усложнение формы и может быть причинной увеличения количества багов.

Но это актуально только в случае больших и сложных форм.

Angular 2 предоставляет и другие пути создания форм.

Модель-ориентированные (Model Driven) или реактивные формы (Reactive) формы

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

Для создания подобных форм в начале нам нужно импортировать соотвествующие библиотеки:

 

Обратите внимание здесь мы импоритировали ReactiveFormsModule вместо FormsModule. Это загрузит нам директивы реактивных форм вместо шаблон-ориентированных директив.

Если нам понадобяться оба типа форм то и оба типа библиотек нам нужно будет импортировать, но об этом ниже.

Наша первая реактиваная форма

Возмем наш прошлый пример и перепишем его в реактивном стиле:

 

Здесь есть несколько отличий. Во первых ниличие директивы formGroup которая связывает форму с переменой контроллера form.

Так же обратите внимание на отсуствие атрибута required. Это означает что логика валидации должна находится в контроллере, где так же можно проводить юнит тестирования.

Как в этом случае должен выглядить контроллер?

Количество кода будет несколько больше, чем в примерах выше:

 

Здесь мы можем увидеть что форма в действительности состоит из FormControl, которые отслеживает глобальное состояние формы. FormBuilder обертка которая объединяет разные FormControl. Каждый такой FormControl задается атрибутами в виде массива.

Первый элемент этого массива начальное состояние поля, оставшиеся элементы это валидаторы.

Достоинства и недостатки модель-ориентированных форм

Одно из очевидных достоинств это возможность юнит тестирования.

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

Классы FormGroup и FormControl обеспечивают API которое позволяет строить UI используя функциональный или как его еще называют реактивный стиль программирования.

Недостатком этого подхода будет то что он требует написания больше кода в контроллере.

Функциональное реактивное программирование в Angular 2

Написанное ниже взято из блог поста, но главная идея состоит в том что контроллеры форм и сами формы сейчас обеспечивают Observable-based API. Вы можете думать о источниках данных (observables) как о коллекции значений изменяющихся в течение времени.

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

Для примера, подпишемся на поток изменения данных от формы используя Observable API:

 

Здесь берется поток данных от формы (данные обновляются при каждом вводе в поле формы), и затем передаем этот поток на несколько функциональных операторов: map и filter.

В действительности этих функций может быть намного больше.

В нашем случае мы переводим все введеные символы в верхний регистр используя map и возращаем только валидный результат используя filter.

Преимущества постороения UI используя функциональное программирование (Functional Reactive Programming FRP)

Мы не обязаны использовать FRP технологию с Angular 2 модель-ориентироваными формами. Но используя их мы пожем получить более понятный и простой код который намного легче обкладывать юнит тестами.

Так с помощью FRP мы можем легко реализовать то что было бы сложно делать при обычном подходе. Например:

  • фоновое сохранение формы перед изменения состояние формы (для примера сохранения не валидного значения в куки для дальнейшего использования)
  • такой привычный нам функционал как восстановить/вернуть (undo/redo)

Взляните на это видео Introduction to Functional Reactive Programming — Using the Async Pipe — Pitfalls to Avoid, часть Angular 2 Services и HTTP курса для получение больше информации о функциональном программирование в Angular 2.

Обновление значений в форме

Теперь у нас есть API для обновления как все формы так и для отдельных полей. Для примера давай те создадим пару кнопок для нашей формы:

Здесь две кнопки для обновления формы, одна для частичного обновления формы и другие для обновление. Далее методы для этой формы:

 

Мы можем видеть что FormGroup обеспечивает нас двумя API методами для обновления значений:

  • patchValue() частичное обновление формы. Этот метод не обязательно должен получть значение всех полей.
  • так же setValue(), который должен получить все значения полей формы иначе будет сообщение о ошибке, что для какого поля нет значений.

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

Это не будет так работат, потому что статусы pristine и untouched не сбросяться на начальные значения.

Как сбросить форму

Обратите внимание на метод reset():

 

Можно ли смешивать шаблон-ориентированных и модель-ориентированные формы?

Модель и шаблон-ориентированные формы реализованы одинаковым образом: для всех форм используется FormGroup, и для каждого поля экземпляр FormControl.

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

  • мы можем использовать ngModel для чтения данных, и FormBuilder для валидации. Для этого нам не обязательно подписываться на поток данных или использовать RxJs.
  • Мы можем объявить поле в контролле и затем обработать его состояние в шаблоне

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

Резюме

Angular 2 предоставляет два пути создания форм: шаблон-ориенитированный и модель-ориентированный.

Шаблон-ориентиролванном подход очень похож на поход применяемый для создание форм в Angular 1, и идеально подходить для миграции с Angular 1 на Angular 2.

Модель-ориентированных подход  позволяет строить более сложное UI.

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

It's only fair to share...Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedInShare on VK