Источник: Nuances of Programming
Проект в Stackblitz со всеми примерами в конце поста.
Переиспользуемые элементы управления
Проблема
Однажды я писал модуль аутентификации для компании в сфере электронной коммерции. Это кажется просто, но позже я понял: в таком модуле 8 разных страниц:
- Вход.
- Регистрация.
- Сброс пароля.
- Вход через социальные сети.
- Слияние аккаунтов и ещё 3 страницы.
На большинстве из них были одни и те же элементы, одинаковый интерфейс, проверки и сообщения об ошибках.
Рассмотрим поле ввода электронной почты. Вначале оно пустое. Если ввод корректен, указываем на это галочкой, а если нет — показываем сообщение об ошибке. Я подсчитал: в таком простом проекте это встречалось 12 раз!
Решение
Итак, мы хотим создать переиспользуемый элемент управления. Первое, что приходит в голову — сделать его компонентом.
Такой компонент должен:
- Иметь поля с типами text и password.
- Проверять ввод регулярным выражением.
- Показывать результат проверки.
- Сообщать об ошибках.
Начнём с простого компонента с @Input():
Выше мы видим 4 части шаблона:
- Поле ввода. У него будет директива ngModel.
- Знак ✓, если ввод корректен.
- Знак *, если ввод обязателен.
- Сообщение об ошибке, если ввод некорректен.
Проверяем:
Результат, страница First Try в примерах:
В чём ошибка? Мы прикрепили директиву формы туда, где её не должно быть. Angular не знает, что наш компонент — элемент управления.
Решение — интерфейс ControlValueAccessor:
Нам нужен ControlValueAccessor, посредник между API форм и нативными элементами. Он сообщает Angular, что элементу доступны директивы форм. У этого интерфейса 4 метода, 3 из них обязательны:
Реализуем наш элемент с его помощью:
Шаблон generic-input.component:
Но этого недостаточно. Мы должны указать токен NG_VALUE_ACCESSOR в метаданных компонента:
Валидаторы
Мы создали пользовательский элемент управления, а теперь реализуем Validator для проверки ввода:
Не забудьте о токене NG_VALIDATORS:
Попробуем ещё раз:
Пока не видно, корректен ввод в элементе или нет.
Получение ссылки на элемент
Мы хотим показать, верен ли ввод. Но у нас нет экземпляра элемента управления. Может быть, мы можем внедрить зависимость, чтобы получить его? Да, это возможно!
constructor(@Self() public controlDir: NgControl) {
this.control.valueAccessor = this;
}
Важные замечания:
- Мы внедряем NgControl, родительский для formControlName и ngModel, не связывая его с каким-либо шаблоном или реактивным модулем.
- Декорируем его с помощью @Self(). Это гарантирует, что он не будет перезаписан деревом инжекторов.
- Устанавливаем его valueAccessor. Он должен указывать на GenericComponent.
Обновим шаблон и используем ссылку:
NgControl уже предоставляет NG_VALUE_ACCESSOR и NG_VALIDATOR. Удаляем их, чтобы не возникла циклическая зависимость:
траница Login & Register:
Формы — компоненты
Проблема
Представьте безумное: вы хотите использовать одну и ту же форму в нескольких местах. Помните, как дважды приходилось заполнять форму адреса, когда платёжный адрес не совпадал с адресом доставки?
Не хочется снова и снова делать одно и то же. Мы хотим написать только одну форму и переиспользовать её. Переиспользовать? Значит, это компонент!
Снова ControlValueAccessor
Создаём AddressFormComponent:
Шаблон:
И опять ControlValueAccessor, но теперь чтобы обернуть всю форму:
Не забудьте о NG_VALUE_ACCESSOR:
Reusable forms — ControlValueAccessor:
Альтернатива
Утомил ControlValueAccessor? Меня тоже. При переиспользовании всей формы можно внедрить ControlContainer:
Запомните: в коде viewProviders, а не providers. Причина в декораторе @Host(). Он используется при внедрении ControlContainer в FormControlName и NgModel. Проверьте директивы FormControlName и NgModel в исходниках.
После предоставления ControlContainer мы можем внедрить его в AddressFormComponent и установить форму адреса равной форме в ControlContainer:
Reusable forms — SubForms:
Просто и коротко, но ограниченно
Как только мы предоставили 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