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

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

Реактивные формы (Angular reactive forms) построены на основе механизма, использующего реактивный подход к программированию.

Для их использования нужно импортировать модуль ReactiveFormsModule.

Создание и валидация Angular reactive forms осуществляется прямо в контроллере. В шаблоне привязывается уже определенная в компоненте модель.

FormGroup и FormControl

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

Группа представляет собой объект FormGroup, а поле — объект FormControl. Оба класса импортируются из @angular/forms.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component({
  selector: 'reactive-form-example',
  templateUrl: './reactive-form-example.component.html',
})
export class ReactiveFormExampleComponent {
  buyTicketForm: FormGroup

  constructor() {
    this._createForm()
  }

  private _createForm() {
    this.buyTicketForm = new FormGroup({
      passenger: new FormControl(null),
      passengerAge: new FormControl(null),

      passengerContacts: new FormGroup({
        telegram: new FormControl(null),
        whatsapp: new FormControl(null),
      }),
    })
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<form [formGroup]="buyTicketForm" novalidate>
  <div>
    <label>Passenger</label>
    <input type="text" formControlName="passenger" />
  </div>

  <div>
    <label>Age</label>
    <input type="number" formControlName="passengerAge" />
  </div>

  <div formGroupName="passengerContacts">
    <div>
      <label>Telegram</label>
      <input type="text" formControlName="telegram" />
    </div>

    <div>
      <label>Whatsapp</label>
      <input type="text" formControlName="whatsapp" />
    </div>
  </div>
</form>

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

1
new FormControl({ value: null, disabled: true });

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

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

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

1
<input type="text" formControlName="MOBILE_PHONE" />

А если задается через переменную, то используется запись с квадратными скобками (подобно передаче значения через @Input() свойство).

1
fieldName: string = 'MOBILE_PHONE'; //в контроллере
1
2
<!--в шаблоне-->
<input type="text" [formControlName]="fieldName" />

Основные поля объекта реактивной формы Angular:

  • controls — поля, включая вложенные FormGroup;
  • errors — содержит ошибки валидации;
  • status — строка, определяющая правильность заполнения формы, значение либо VALID, либо INVALID;
  • validtrue, если форма валидна;
  • invalidtrue, если форма невалидна;
  • pristinetrue, если не было взаимодействия с полями;
  • touchedtrue, если одно из полей становилось активным (получало фокус);
  • dirtytrue, если пользователь заполнил хотя бы одно из полей;
  • value — значение формы в виде объекта;
  • statusChanges — позволяет отслеживать изменение статуса валидности;
  • valueChanges — позволяет отслеживать изменение значения.

Важно

Неактивные (disabled) поля формы не входят в ее значение и будут отсутствовать в поле value. Для получения полной модели имеется getRawValue().

Реактивные формы позволяют обращаться к отдельному полю используя метод get(), которому передается в виде строки наименование поля.

1
2
3
this.loginForm.get('login'); //поле
this.loginForm.get('address'); //вложенная группа
this.loginForm.get('address.city'); //поле вложенной группы

Отслеживание изменений формы осуществляется через подписку на valueChanges Observable. Функция обработчик принимает параметром значение формы.

1
2
3
this.loginForm.valueChanges.subscribe((v) => {
    console.log(v);
});

Использовать valueChanges можно применительно к отдельному полю.

1
2
3
this.loginForm.get('login').valueChanges.subscribe((v) => {
    console.log(v);
});

Для отслеживания изменения статуса поля или формы в целом "подписывайтесь" на statusChanges.

1
2
3
this.loginForm.statusChanges.subscribe((status) => {
    console.log(status);
});

Для сброса значений полей формы или полей одной из ее групп используется метод reset(), который принимает объект с начальным значением.

1
2
3
4
5
//Всем полям будет присвоено null
this.loginForm.reset();

//Полю login будет присвоено 'default_login', остальным - null
this.loginForm.reset({ login: 'default_login' });

Для динамического изменения структуры Angular reactive forms предусмотрен ряд методов:

  • addControl(name: string, value: any) — добавляет новое поле соответствующей группе;
  • setControl(name: string, value: any) — заменяет уже существующее поле соответствующей группы;
  • removeControl(name: string) — удаляет поле из группы.

patchValue() и setValue()

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

Методу setValue() должен передаваться объект, полностью совпадающий по строению с описанной моделью формы, а patchValue() — лишь часть этой структуры.

1
2
3
4
5
this.loginForm.patchValue({ login: 'user123' });
this.loginForm.setValue({
    login: 'user123',
    password: 'pwd123',
});

Если setValue() передать "неполную" модель, будет сгенерирована ошибка.

Вторым параметром оба метода принимают объект, с помощью которого, например, можно сделать так, чтобы установка значения Angular reactive forms не инициировала событие valueChanges.

1
2
3
4
this.loginForm.patchValue(
    { login: 'user123' },
    { emitEvent: false }
);

FormArray

Для вывода (или динамического добавления) одной и той же группы или отдельного поля более, чем один раз, необходим экземпляр класса FormArray.

В представлении для обозначения сущности FormArray используется formArrayName. Причем не должно быть совпадений значений formGroupName или formControlName в пределах всей формы.

Пример с полем.

1
2
3
4
5
6
7
userForm: FormGroup = new FormGroup({
  fullName: new FormControl(''),
  numbers: new FormArray([
    new FormControl(''),
    new FormControl(''),
  ]),
})
1
2
3
4
5
6
<div
  formArrayName="numbers"
  *ngFor="let item of userForm.get('numbers').controls; let i = index;"
>
  <input type="text" [formControlName]="i" />
</div>

Пример с группой.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
userForm: FormGroup = new FormGroup({
  fullName: new FormControl(''),
  children: new FormArray([
    new FormGroup({
      fullName: new FormControl(''),
      age: new FormControl(''),
    }),
    new FormGroup({
      fullName: new FormControl(''),
      age: new FormControl(''),
    }),
  ]),
})
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<div
  formArrayName="numbers"
  *ngFor="let item of userForm.get('children').controls; let i = index;"
>
  <div [formGroupName]="i">
    <div>
      <label>Name</label>
      <input type="text" formControlName="fullName" />
    </div>

    <div>
      <label>Age</label>
      <input type="text" formControlName="age" />
    </div>
  </div>
</div>

Angular Form Builder

В качестве альтернативы можно создать реактивную форму (и настоятельно рекомендуется) с Angular FormBuilder.

Его использование помогает избежать повторений и повышает читабельность кода.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@Component({
    selector: 'reactive-form-example',
    templateUrl: './reactive-form-example.component.html',
})
export class ReactiveFormExampleComponent {
    buyTicketForm: FormGroup;

    constructor(private fb: FormBuilder) {
        this._createForm();
    }

    private _createForm() {
        this.buyTicketForm = this.fb.group({
            passenger: '',
            passengerAge: '',
        });
    }
}

Шаблон остается неизменным.

Сперва в зависимостях компонента необходимо указать FormBuilder, далее создается свойство компонента типа FormGroup, которому в методе _createForm() передается создаваемая модель. Метод вызывается в конструкторе компонента.

Модель также может содержать дочерние FormGroup, созданные с помощью Angular Form Builder.

1
2
3
4
5
6
7
8
this.buyTicketForm = this.fb.group({
    passenger: '',
    passengerAge: '',
    passengerContacts: this.fb.group({
        telegram: '',
        whatsapp: '',
    }),
});

Ссылки

Комментарии