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

Я предполагаю, что вы знаете, что такое формы, и вы работали с реактивными формами Angular.

Мы рассмотрим два подхода к достижению этого:

  1. Использование ControlContainer, который позволяет нам передавать родительскую форму ее подформам, которые реализованы как компоненты.
  2. И @ViewChild, который помогает нам получить экземпляр класса компонента, который в данном случае будет экземпляром компонента формы.

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

Демо пользовательского интерфейса форм

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

  • HeroComponent (родительский)
  • PowersComponent (подформа, запрошенная с помощью декоратора @ViewChild)
  • HobbiesComponent (подформа, реализованная с использованием класса ControlContainer)

Реализация подформы с использованием декоратора @ViewChild

Давайте начнем с наилучшего подхода, который использует декоратор, который позволяет родительской форме (HeroComponent) запрашивать экземпляр класса компонента нашей дочерней / подформы, которая является PowersComponent, см. код ниже:

Как вы можете видеть в шаблоне HeroComponent, компонент PowersComponent не требует дополнительных входных данных.

<nb-card>
  <nb-card-header>Super Power</nb-card-header>
  <nb-card-body class="col">
    <app-powers></app-powers> // here
  </nb-card-body>
</nb-card>

Но если вы можете проверить класс HeroComponent, вы заметите, как мы получаем экземпляр класса компонента подформы Powers и вызываем метод createFormGroup общедоступная функция-член, чтобы вернуть нам экземпляр конфигурации PowersComponent FormGroup.

@ViewChild(PowersComponent, { static: true }) public powersComponent: PowersComponent;

Обратите внимание, как параметры @ViewChild используют static: true для разрешения экземпляра компонента как можно скорее, чтобы у нас есть экземпляр подформы класса PowersComponent.

И проверьте строку 21 файла HeroComponent.ts, который создает форму PowersComponent.

powers: this.powersComponent.createFormGroup(),

Речь идет о том, как создать повторно используемую и реактивную подформу с помощью @ViewChild decorator, к которому я склонен, потому что считаю его простым в реализации и более легким в обслуживании в качестве подформы и родительские формы отделены друг от друга и обеспечивают следующие преимущества:

  • Родительская форма вообще не должна знать об экземпляре подчиненной формы. Все, что ему нужно, - это чтобы у класса компонента подформы была открытая функция-член createFormGroup, которая возвращает экземпляр FormGroup.
  • Изменение конфигурации формы подчиненной формы никоим образом не влияет на конфигурацию родительской формы или ее шаблон.

Круто, а как насчет настройки модульного теста для такой настройки?

Просто проверьте код ниже

Модульный тест для подхода @ViewChild

Если вы проверите строки 11 и 25 приведенного выше кода, вы заметите, как мы создаем поддельный компонент PowersComponent с поддельной функцией, которая имитирует функцию createFormGroup с помощью createSpyObj функция.

const powersComponent = jasmine.createSpyObj('PowersComponent', ['createFormGroup']);  // line 11
...
component.powersComponent = powersComponent; // line 25

Без указанного выше в файле спецификации HeroComponent вы получите сообщение об ошибке при выполнении тестов, которые:

TypeError: Cannot read property 'createFormGroup' of undefined

Реализация подформы с помощью ControlContainer

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

В шаблоне HeroComponent у нас есть разметка другой нашей подформы, HobbiesComponent

<app-hobbies [parentForm]="heroForm [formGroup]="heroForm.get('hobbies')"
></app-hobbies>

Как видите, эта разметка формы использует директиву [formGroup], которая поставляется с ControlContainer, позволяя родительской форме передавать подчиненную форму самой подчиненной форме. , при необходимости.
Также обратите внимание, что подчиненная форма ожидает ввода, который должен быть родительской формой, это также позволяет нам иметь доступ к родительской форме внутри подчиненной формы, если необходимо, см. источник этой вложенной формы -форма ниже.

Модульный тест для этой настройки выглядит следующим образом.

Создайте базовую заглушку HobbiesComponent, которая будет записью в объявлениях TestBed родительской формы.

Это все, что нам нужно в родительской форме, чтобы настроить тест для подчиненной формы с помощью ControlContainer.

А ниже приведены спецификации компонента HobbiesComponent.

Если вы посмотрите на исходный код выше, то увидите, что мы создаем фиктивную FormGroup, используя фиктивный FormBuilder, определенный в строке 9. Затем мы используем фиктивный FormGroup как токен для ControlContainer в строке 18.

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

  • Если подчиненная форма изменяется, возможно, имя свойства было переименовано, родительская форма должна иметь конфигурацию подчиненной формы, которая совпадает с этим изменением для подчиненной формы.
  • Если родительская форма изменяется в конфигурации подчиненной формы, подчиненной форме необходимо знать об этом, поэтому для обновления шаблона, например, значение formControlName или любой другой код в подчиненной форме, которая использует экземпляр подчиненной формы FromGroup.
  • Необходимость писать заглушки для всех компонентов вашей подформы, чтобы избежать каких-либо предупреждений в тестах.
  • Если вы хотите повторно использовать эту форму в любой другой родительской форме, эта форма должна иметь конфигурацию для этой подформы.
hobbies: this.formBuilder.group({
  favoriteHobby: ['', Validators.required]
})

Резюме

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

Последний подход работает, но имеет множество недостатков.

Но, в конце концов, это зависит от вас, потому что это ваше мышление, ваш код.

Надеюсь, вам понравилось читать и вы кое-что узнали.

Вы можете ознакомиться с полным исходным кодом для этого на GitHub.