Перейти к содержанию

Введение в формы в Angular

📅 28.02.2022

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

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

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

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

Предварительные условия

Это руководство предполагает, что у вас есть базовое понимание следующего.

Выбор подхода

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

Формы Подробности
Реактивные формы Обеспечивают прямой, явный доступ к объектной модели формы. По сравнению с формами, управляемыми шаблонами, они более надежны: они более масштабируемы, многократно используемы и тестируемы. Если формы являются ключевой частью вашего приложения или вы уже используете реактивные паттерны для построения приложения, используйте реактивные формы.
Формы, управляемые шаблонами Опираются на директивы в шаблоне для создания и манипулирования базовой объектной моделью. Они полезны для добавления простой формы в приложение, например, формы подписки на список адресов электронной почты. Их легко добавить в приложение, но они не так хорошо масштабируются, как реактивные формы. Если у вас очень простые требования к форме и логика, которой можно управлять исключительно в шаблоне, формы, управляемые шаблоном, могут быть хорошим вариантом.

Ключевые различия

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

Реактивный Управляемый шаблонами
Настройка модели формы Явная, созданная в классе компонента Неявная, созданная директивами
Модель данных Структурированная и неизменяемая Неструктурированная и изменяемая
Поток данных Синхронный Асинхронный
Валидация формы Функции директивы

Масштабируемость

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

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

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

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

Абстракция форм, управляемых шаблонами, также влияет на тестирование.

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

Настройка модели формы

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

Общие классы основы формы

Как реактивные, так и шаблонные формы строятся на следующих базовых классах.

Базовые классы Детали
FormControl Отслеживает значение и статус проверки отдельного элемента управления формы.
FormGroup Отслеживает те же значения и статус для коллекции элементов управления формы.
FormArray Отслеживает те же значения и статус для массива элементов управления формы.
ControlValueAccessor Создает мост между экземплярами Angular FormControl и встроенными элементами DOM.

Установка в реактивных формах

В реактивных формах вы определяете модель формы непосредственно в классе компонента. Директива [formControl] связывает явно созданный экземпляр FormControl с конкретным элементом формы в представлении, используя внутренний аксессор значения.

Следующий компонент реализует поле ввода для одного элемента управления, используя реактивные формы. В этом примере моделью формы является экземпляр FormControl.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
    selector: 'app-reactive-favorite-color',
    template: `
        Favorite Color:
        <input
            type="text"
            [formControl]="favoriteColorControl"
        />
    `,
})
export class FavoriteColorComponent {
    favoriteColorControl = new FormControl('');
}

Рисунок 1 показывает, как в реактивных формах модель формы является источником истины; она предоставляет значение и статус элемента формы в любой момент времени через директиву [formControl] на элементе ввода.

Рисунок 1. Прямой доступ к модели формы в реактивной форме.

Reactive forms key differences

Настройка в формах, управляемых шаблонами

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import { Component } from '@angular/core';

@Component({
    selector: 'app-template-favorite-color',
    template: `
        Favorite Color:
        <input type="text" [(ngModel)]="favoriteColor" />
    `,
})
export class FavoriteColorComponent {
    favoriteColor = '';
}

В форме, управляемой шаблоном, источником истины является шаблон. У вас нет прямого программного доступа к экземпляру FormControl, как показано на рисунке 2.

Рисунок 2. Прямой доступ к модели формы в форме, управляемой шаблоном.

Template-driven forms key differences

Поток данных в формах

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

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

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

Поток данных в реактивных формах

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

Диаграмма "представление — модель" показывает, как происходит движение данных при изменении значения поля ввода из представления посредством следующих шагов.

  1. Пользователь вводит значение в элемент ввода, в данном случае любимый цвет Синий.

  2. Элемент ввода формы выдает событие "input" с последним значением.

  3. Аксессор значения элемента управления, прослушивающий события на элементе ввода формы, немедленно передает новое значение экземпляру FormControl.

  4. Экземпляр FormControl передает новое значение через наблюдаемую valueChanges.

  5. Все подписчики наблюдаемой valueChanges получают новое значение.

Reactive forms data flow - view to model

Диаграмма "модель — представление" показывает, как программное изменение в модели распространяется на представление посредством следующих шагов.

  1. Пользователь вызывает метод favoriteColorControl.setValue(), который обновляет значение FormControl.

  2. Экземпляр FormControl передает новое значение через наблюдаемую valueChanges.

  3. Все подписчики наблюдаемой valueChanges получают новое значение.

  4. Аксессор значения элемента управления на элементе ввода формы обновляет элемент новым значением.

Reactive forms data flow - model to view

Поток данных в формах, управляемых шаблонами

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

Диаграмма "представление — модель" показывает, как происходит движение данных при изменении значения поля ввода из представления посредством следующих шагов.

  1. Пользователь вводит Синий в элемент ввода.

  2. Элемент ввода испускает событие "input" со значением Blue.

  3. Аксессор значения элемента управления, присоединенный к вводу, вызывает метод setValue() на экземпляре FormControl.

  4. Экземпляр FormControl передает новое значение через наблюдаемую valueChanges.

  5. Все подписчики наблюдаемой valueChanges получают новое значение.

  6. Аксессор значения элемента управления также вызывает метод NgModel.viewToModelUpdate(), который испускает событие ngModelChange.

  7. Поскольку шаблон компонента использует двустороннее связывание данных для свойства favoriteColor, свойство favoriteColor в компоненте обновляется до значения, выдаваемого событием ngModelChange (Blue).

Template-driven forms data flow - view to model

Диаграмма модель-вид показывает, как данные перетекают из модели в представление, когда favoriteColor меняется с Blue на Red, посредством следующих шагов

  1. Значение favoriteColor обновляется в компоненте.

  2. Начинается обнаружение изменений.

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

  4. Метод ngOnChanges() ставит в очередь асинхронную задачу для установки значения для внутреннего экземпляра FormControl.

  5. Обнаружение изменений завершается.

  6. На следующем тике выполняется задача установки значения экземпляра FormControl.

  7. Экземпляр FormControl передает последнее значение через наблюдаемую valueChanges.

  8. Все подписчики наблюдаемой valueChanges получают новое значение.

  9. Аксессор значения элемента управления обновляет элемент ввода формы в представлении последним значением favoriteColor.

Template-driven forms data flow - model to view

Мутабельность модели данных

Метод отслеживания изменений играет важную роль в эффективности вашего приложения.

Формы Детали
Реактивные формы Сохраняйте чистоту модели данных, предоставляя ее в виде неизменяемой структуры данных. Каждый раз, когда в модели данных происходит изменение, экземпляр FormControl возвращает новую модель данных, а не обновляет существующую модель данных. Это дает вам возможность отслеживать уникальные изменения в модели данных через наблюдаемый элемент управления. Обнаружение изменений более эффективно, поскольку обновлять нужно только уникальные изменения. Поскольку обновление данных происходит по реактивной схеме, вы можете интегрировать операторы observable для преобразования данных.
Формы на основе шаблонов Положитесь на изменяемость с двусторонней привязкой данных, чтобы обновлять модель данных в компоненте по мере внесения изменений в шаблон. Поскольку при использовании двусторонней привязки данных нет уникальных изменений, которые нужно отслеживать в модели данных, обнаружение изменений менее эффективно при определении необходимости обновления.

Разница продемонстрирована в предыдущих примерах, где используется элемент ввода favorite-color.

  • В реактивных формах экземпляр FormControl всегда возвращает новое значение, когда значение элемента управления обновляется.
  • В формах, управляемых шаблонами, свойство favorite color всегда изменяется на новое значение

Валидация формы

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

Формы Подробности
Реактивные формы Определите пользовательские валидаторы как функции, которые получают элемент управления для проверки.
Формы, управляемые шаблонами Связаны с директивами шаблона и должны предоставлять пользовательские директивы валидаторов, которые оборачивают функции валидации.

Для получения дополнительной информации смотрите Form Validation.

Тестирование

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

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

Следующие примеры демонстрируют процесс тестирования форм с реактивными и управляемыми шаблонами формами.

Тестирование реактивных форм

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

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

Проверка потока данных от представления к модели

В первом примере выполняются следующие шаги для проверки потока данных от представления к модели.

  1. Запросите представление для элемента ввода формы и создайте пользовательское событие "ввод" для проверки.

  2. Установите новое значение для входа Red и отправьте событие "input" на элемент ввода формы.

  3. Убедитесь, что значение favoriteColorControl компонента совпадает со значением из ввода.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
it('should update the value of the input field', () => {
    const input = fixture.nativeElement.querySelector(
        'input'
    );
    const event = createNewEvent('input');

    input.value = 'Red';
    input.dispatchEvent(event);

    expect(
        fixture.componentInstance.favoriteColorControl.value
    ).toEqual('Red');
});

В следующем примере выполняются следующие шаги для проверки потока данных от модели к представлению.

  1. Используйте favoriteColorControl, экземпляр FormControl, для установки нового значения.

  2. Запросите представление для элемента ввода формы.

  3. Убедитесь, что новое значение, установленное в элементе управления, совпадает со значением в элементе ввода.

1
2
3
4
5
6
7
8
9
it('should update the value in the control', () => {
    component.favoriteColorControl.setValue('Blue');

    const input = fixture.nativeElement.querySelector(
        'input'
    );

    expect(input.value).toBe('Blue');
});

Тестирование форм, управляемых шаблонами

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

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

Следующий тест проверяет поток данных от представления к модели.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
it('should update the favorite color in the component', fakeAsync(() => {
    const input = fixture.nativeElement.querySelector(
        'input'
    );
    const event = createNewEvent('input');

    input.value = 'Red';
    input.dispatchEvent(event);

    fixture.detectChanges();

    expect(component.favoriteColor).toEqual('Red');
}));

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

  1. Запросите представление для элемента ввода формы и создайте пользовательское событие "ввод" для теста.
  2. Установите новое значение для входа Red и отправьте событие "input" на элемент ввода формы.
  3. Запустите обнаружение изменений в тестовом приспособлении.
  4. Убедитесь, что значение свойства компонента favoriteColor совпадает со значением из ввода.

Следующий тест проверяет поток данных от модели к представлению.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
it('should update the favorite color on the input field', fakeAsync(() => {
    component.favoriteColor = 'Blue';

    fixture.detectChanges();

    tick();

    const input = fixture.nativeElement.querySelector(
        'input'
    );

    expect(input.value).toBe('Blue');
}));

Ниже перечислены шаги, выполняемые при тестировании модели для просмотра.

  1. Используйте экземпляр компонента для установки значения свойства favoriteColor.
  2. Запустите обнаружение изменений с помощью тестового приспособления.
  3. Используйте метод tick() для имитации течения времени в задаче fakeAsync().
  4. Запросите представление для элемента ввода формы.
  5. Убедитесь, что вводимое значение соответствует значению свойства favoriteColor в экземпляре компонента.

Следующие шаги

Чтобы узнать больше о реактивных формах, см. следующие руководства:

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

Комментарии