Найти тему
Nuances of programming

Переиспользование форм в Angular

Оглавление

Источник: Nuances of Programming

Проект в Stackblitz со всеми примерами в конце поста.

Переиспользуемые элементы управления

Проблема

Однажды я писал модуль аутентификации для компании в сфере электронной коммерции. Это кажется просто, но позже я понял: в таком модуле 8 разных страниц:

  • Вход.
  • Регистрация.
  • Сброс пароля.
  • Вход через социальные сети. 
  • Слияние аккаунтов и ещё 3 страницы. 

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

Рассмотрим поле ввода электронной почты. Вначале оно пустое. Если ввод корректен, указываем на это галочкой, а если нет  —  показываем сообщение об ошибке. Я подсчитал: в таком простом проекте это встречалось 12 раз!

-2

Решение

Итак, мы хотим создать переиспользуемый элемент управления. Первое, что приходит в голову  —  сделать его компонентом.

Такой компонент должен:

  • Иметь поля с типами text и password.
  • Проверять ввод регулярным выражением.
  • Показывать результат проверки.
  • Сообщать об ошибках.

Начнём с простого компонента с @Input():

-3

Выше мы видим 4 части шаблона:

  • Поле ввода. У него будет директива ngModel. 
  • Знак ✓, если ввод корректен.
  • Знак *, если ввод обязателен. 
  • Сообщение об ошибке, если ввод некорректен.

Проверяем:

-4

Результат, страница First Try в примерах:

-5

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

Решение  —  интерфейс ControlValueAccessor:

Нам нужен ControlValueAccessor, посредник между API форм и нативными элементами. Он сообщает Angular, что элементу доступны директивы форм. У этого интерфейса 4 метода, 3 из них обязательны:

-6

Реализуем наш элемент с его помощью:

-7

Шаблон generic-input.component:

-8

Но этого недостаточно. Мы должны указать токен NG_VALUE_ACCESSOR в метаданных компонента:

-9

Валидаторы

Мы создали пользовательский элемент управления, а теперь реализуем Validator для проверки ввода:

-10

Не забудьте о токене NG_VALIDATORS:

-11

Попробуем ещё раз:

-12

Пока не видно, корректен ввод в элементе или нет.

Получение ссылки на элемент

Мы хотим показать, верен ли ввод. Но у нас нет экземпляра элемента управления. Может быть, мы можем внедрить зависимость, чтобы получить его? Да, это возможно!

constructor(@Self() public controlDir: NgControl) {
this.control.valueAccessor = this;
}

Важные замечания:

  • Мы внедряем NgControl, родительский для formControlName и ngModel, не связывая его с каким-либо шаблоном или реактивным модулем.
  • Декорируем его с помощью @Self(). Это гарантирует, что он не будет перезаписан деревом инжекторов.
  • Устанавливаем его valueAccessor. Он должен указывать на GenericComponent.

Обновим шаблон и используем ссылку:

-13

NgControl уже предоставляет NG_VALUE_ACCESSOR и NG_VALIDATOR. Удаляем их, чтобы не возникла циклическая зависимость:

-14

траница Login & Register:

-15

Формы  —  компоненты

Проблема

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

market.co.uk checkout proccess, using the same form twice
market.co.uk checkout proccess, using the same form twice

Не хочется снова и снова делать одно и то же. Мы хотим написать только одну форму и переиспользовать её. Переиспользовать? Значит, это компонент!

Снова ControlValueAccessor

Создаём AddressFormComponent:

-17

Шаблон:

-18

И опять ControlValueAccessor, но теперь чтобы обернуть всю форму:

-19

Не забудьте о NG_VALUE_ACCESSOR:

-20

Reusable forms  —  ControlValueAccessor:

-21

Альтернатива

Утомил ControlValueAccessor? Меня тоже. При переиспользовании всей формы можно внедрить ControlContainer:

-22

Запомните: в коде viewProviders, а не providers. Причина в декораторе @Host(). Он используется при внедрении ControlContainer в FormControlName и NgModel. Проверьте директивы FormControlName и NgModel в исходниках.

После предоставления ControlContainer мы можем внедрить его в AddressFormComponent и установить форму адреса равной форме в ControlContainer:

-23

Reusable forms  —  SubForms:

-24

Просто и коротко, но ограниченно

Как только мы предоставили FormGroupDirective или ngModelGroup, то создали связь только с одной реализацией форм (шаблонной или реактивной).

Демо

<figure><iframe width="700" height="376" src="/media/4ce26a2b4ab6df0109c47281ea6e86fc" allowfullscreen=""></iframe></figure>

Итоги

Вот, что мы узнали:

  • ControlValueAccessor  —  мост между нашими компонентами и API формами. Он позволяет создавать настраиваемые, переиспользуемые элементы управления и формы.
  • Внедрение зависимости поможет использовать NgControl и его valueAccessor для простого доступа к элементу в шаблоне.
  • Можно снова внедрить зависимости, чтобы добавить FormGroupDirective или ngModelGroup и создать подформу.

Читайте также:

А еще вы можете проверить ваши знания:

Читайте нас в телеграмме и vk

Перевод статьи Eliran Eliassy: Reducing the forms boilerplate  —  make your Angular forms reusable