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

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

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

Вот как мы создаем эту форму в коде нашего приложения:

Класс компонента формы

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  form: FormGroup;

  constructor(private formBuilder: FormBuilder) { }

  ngOnInit(): void {
    this.form = this.formBuilder.group({
      firstName: [{ value: 'Foo', disabled: true }, [Validators.required]],
      lastName: ['Bar']
    });
  }

  onSubmit(): void { }

  get firstName(): FormControl {
    return this.form.controls.firstName as FormControl;
  }

  get lastName(): FormControl {
    return this.form.controls.lastName as FormControl;
  }
}

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

Шаблон формы

<form [formGroup]="form">
 <h1>Angular Reactive Form</h1>
 <input formControlName="firstName" placeholder="First Name" />
 <input formControlName="lastName" placeholder="Last Name" />
 <button (click)="onSubmit()">Submit</button>
</form>

Здесь я использую директивы formGroup и formControlName из API Reactive Forms.

Использование метода экземпляра Disable() FormControl

Этот метод отключит как элемент управления пользовательского интерфейса, так и экземпляр FormControl. Давайте посмотрим, как мы будем использовать этот метод для отключения элемента управления формы. В функции ngOnInit после создания экземпляра формы мы можем отключить элемент управления формы lastName, как показано ниже.

ngOnInit(): void {
    this.form = this.formBuilder.group({
      firstName: [{ value: 'Foo', disabled: true },  [Validators.required]],
      lastName: ['Bar']
    });

    this.lastName.disable();
  }

Если мы console.log элемент управления lastName, мы увидим, что его экземпляр будет отключен.

Этот способ быстрый и простой.

Когда этот способ отключения FormControls будет недостатком?

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

ngOnInit(): void {
    this.form = this.formBuilder.group({
      firstName: [{ value: 'Foo', disabled: true }, [Validators.required]],
      lastName: ['Bar']
    });

    this.lastName.disable();

    console.log(this.lastName);
    console.log('Form:::', this.form);
  }

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

Последний вывод console.log приведен ниже.

Стоит отметить, что когда все элементы управления формы отключены, независимо от их статуса достоверности, свойства экземпляра элемента управления status будут установлены на «ОТКЛЮЧЕНО», следовательно, статус FormGroup также будет установлен на « ИНВАЛИД".

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

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

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

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

this.form.valueChanges
     .pipe(
       distinctUntilChanged()
     )
     .subscribe(
       (status) => {
         if (this.form.status === 'VALID') {// `this.form.status` is "DISABLED"
           // set some state value
         }
       }
     );
 
   this.lastName.setValue('Baz');

Мы устанавливаем наблюдателя для изменения значения формы и после того, как мы меняем значение элемента управления lastName.

Значение нашей формы не будет синхронизироваться с нашим хранилищем состояний приложения, потому что при выполнении наблюдателя он обнаружит, что статус формы ОТКЛЮЧЕН, даже если значение формы действительно, что может вызвать путаницу и несоответствия в нашем коде, особенно когда у хранилища состояний также есть наблюдатели.

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

Другой способ отключить элементы управления формой — это тот, который мы используем для отключения элемента управления firstName при создании экземпляра самой формы.

this.form = this.formBuilder.group({
      firstName: [{ value: 'Foo', disabled: true }, [Validators.required]],
      lastName: ['Bar']
    });

Это имеет тот же эффект, что описан выше, когда элемент управления отключен с помощью метода элемента управления .disable().

Использование атрибута шаблона для отключения FormControls

Еще один способ — добавить атрибут disabled=true в HTML в шаблоне следующим образом:

<form [formGroup]="form">
 <h1>Angular Reactive Form</h1>
 <input formControlName="firstName" placeholder="First Name">
 <input formControlName="lastName" disabled="true" placeholder="Last Name">
 <button (click)="onSubmit()">Submit</button>
</form>

Что дает следующее предупреждение

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

Это означает, что если отключенный атрибут ожидает динамическое выражение, такое как [disabled]="isDisabled" (результирующее true или false, а не просто static true например) в результате чего привязка может быть проверена во время обнаружения изменений, это сделает привязку восприимчивой к ошибке 'изменено после проверки', то есть выражение может быть false, когда выполняется обнаружение изменений, а затем true, когда выполняется фаза проверки обнаружения изменений.

Подробное описание ошибки ExpressionChangedAfterItHasBeenCheckedError можно найти здесь.

Поэтому, чтобы избежать предупреждения, вы можете использовать привязку [attr.*] следующим образом:

<form [formGroup]="form">
 <h1>Angular Reactive Form</h1>
 <input formControlName="firstName" placeholder="First Name">
 <input formControlName="lastName" [attr.disabled]="true" placeholder="Last Name">
 <button (click)="onSubmit()">Submit</button>
</form>

Предупреждения нет, но стоит отметить, что это добавит отключенный атрибут HTML, который нельзя переключать, используя только true и false (поскольку false также приводит к отключению поля), null и undefined можно использовать для включения поля. Другой способ включить поле с этим атрибутом со значением true, false или любым истинным значением — удалить его из поля.

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

Однако важно отметить, что значение формы не имеет значения элемента управления firstName, поскольку оно отключено. Это происходит, когда некоторые элементы управления формы отключены (используя экземпляр Disable() элемента управления FormControl). метод или из конфигурации формы, например для элемента управления firstName), а некоторые включаются или отключаются с помощью директивы шаблона [disabled].

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

this.form.getRawValue()
console.log(this.form.value);
console.log(this.form.getRawValue());

// The logs will have

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

Другой способ, аналогичный последнему ([attr.disabled]), — использовать атрибут только для чтения.

<form [formGroup]="form">
 <h1>Angular Reactive Form</h1>
 <input formControlName="firstName" placeholder="First Name">
 <input formControlName="lastName" readonly="true" placeholder="Last Name">
 <button (click)="onSubmit()">Submit</button>
</form>

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

Вы можете просто стилизовать ввод, как показано ниже.

input {
   margin-bottom: 8px;
   width: 250px;
   height: 45px;
   border-radius: 3px;
   padding-left: 10px;
   border: 1px solid rgba(0, 0, 0, 0.4);
 
   &:read-only {
     color: rgb(84, 84, 84);
     cursor: default;
     background-color: rgba(239, 239, 239, 0.3);
 
     &:focus {
       outline: none;
     }
   }
 }

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

В заключении

Мы рассмотрели различные способы отключения контроля формы. Они следующие:

  • Использование метода экземпляра Disable() FormControl
  • Отключение FormControl из конфигурации формы
  • Использование атрибута шаблона для отключения элементов FormControl, таких как disable, [attr.disable] и только для чтения

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

Репозиторий Git для демонстрационного проекта находится здесь.