Реактивные формы

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

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

Реактивные формы в Angular строятся на основе двух ключевых элементов: FormControl и FormGroup. Эти классы предоставляют API для управления состоянием формы и валидацией данных. Реактивные формы обеспечивают декларативный подход, где вся логика работы с формой сосредоточена в компоненте.

FormControl

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

Пример создания FormControl:

import { FormControl } from '@angular/forms';

const nameControl = new FormControl('');

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

FormGroup

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

Пример создания FormGroup:

import { FormGroup, FormControl } from '@angular/forms';

const profileForm = new FormGroup({
  firstName: new FormControl(''),
  lastName: new FormControl('')
});

Здесь создается группа с двумя полями: firstName и lastName, которые могут быть использованы как контролы в форме.

Создание формы в компоненте

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

Пример компонента с реактивной формой:

import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';

@Component({
  selector: 'app-profile',
  templateUrl: './profile.component.html'
})
export class ProfileComponent {
  profileForm: FormGroup;

  constructor() {
    this.profileForm = new FormGroup({
      firstName: new FormControl('John'),
      lastName: new FormControl('Doe')
    });
  }

  onSubmit() {
    console.log(this.profileForm.value);
  }
}

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

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

Одним из основных преимуществ реактивных форм является возможность добавления валидации данных. Angular предоставляет несколько встроенных валидаторов, таких как Validators.required, Validators.minLength, Validators.maxLength и другие, которые можно использовать при создании формы.

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

import { FormGroup, FormControl, Validators } from '@angular/forms';

this.profileForm = new FormGroup({
  firstName: new FormControl('', [Validators.required, Validators.minLength(2)]),
  lastName: new FormControl('', Validators.required)
});

В этом примере поле firstName имеет два валидатора: обязательность и минимальную длину в два символа, а поле lastName обязано быть заполнено.

Проверка валидности

Для проверки валидности формы или отдельных контролов можно использовать свойства valid и invalid. Эти свойства позволяют определить, валидна ли форма или ее часть.

if (this.profileForm.valid) {
  console.log('Форма валидна');
} else {
  console.log('Форма содержит ошибки');
}

Управление состоянием формы

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

Отключение и включение полей

Для изменения состояния поля формы можно использовать методы disable и enable:

this.profileForm.get('firstName')?.disable();
this.profileForm.get('firstName')?.enable();

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

Изменение значений полей

Для программного изменения значения поля используется метод setValue или patchValue. Метод setValue устанавливает новые значения для всех полей формы, тогда как patchValue позволяет обновить только часть полей.

this.profileForm.setValue({
  firstName: 'Alice',
  lastName: 'Smith'
});

this.profileForm.patchValue({
  firstName: 'Bob'
});

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

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

Для отображения формы в шаблоне используется директива formGroup, которая связывает форму с компонентом, и директивы formControlName, чтобы связать отдельные поля с контролами.

Пример использования в шаблоне:

<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
  <label for="firstName">First Name</label>
  <input id="firstName" formControlName="firstName">
  <div *ngIf="profileForm.get('firstName')?.invalid && profileForm.get('firstName')?.touched">
    First name is required and must be at least 2 characters long.
  </div>

  <label for="lastName">Last Name</label>
  <input id="lastName" formControlName="lastName">

  <button type="submit" [disabled]="profileForm.invalid">Submit</button>
</form>

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

Динамические формы

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

Пример создания динамических полей:

this.profileForm = new FormGroup({
  dynamicField: new FormControl('')
});

this.addField('newField');

Метод addField может динамически добавлять новые поля в форму:

addField(fieldName: string) {
  this.profileForm.addControl(fieldName, new FormControl(''));
}

Обработка ошибок и кастомные валидаторы

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

Пример кастомного валидатора:

import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';

export function forbiddenNameValidator(name: string): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    const forbidden = control.value === name;
    return forbidden ? { forbiddenName: { value: control.value } } : null;
  };
}

Использование кастомного валидатора в форме:

this.profileForm = new FormGroup({
  username: new FormControl('', [forbiddenNameValidator('admin')])
});

Асинхронная валидация

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

Пример асинхронного валидатора:

import { AbstractControl, ValidationErrors, AsyncValidatorFn } from '@angular/forms';
import { Observable, of } from 'rxjs';

export function uniqueUsernameValidator(control: AbstractControl): Observable<ValidationErrors | null> {
  return of(control.value === 'admin' ? { uniqueUsername: true } : null);
}

Использование асинхронного валидатора:

this.profileForm = new FormGroup({
  username: new FormControl('', [], [uniqueUsernameValidator])
});

Заключение

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