Angular 14: Типовые формы больше не мечта (часть 2)


Введение

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

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

Да :), это не мечта, больше не нужно искать более-менее чистые обходные пути: типизация форм берется нативно фреймворком

Как это работает?
Как интегрировать его в приложения?
Что будет делать схема миграции Angular 14?

FormControl

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

const nameControl = new FormControl<string | null>('');

nameControl(23); // Type Error

const name = nameControl.value // string | null
Войдите в полноэкранный режим Выход из полноэкранного режима

Но почему тип может быть null?

Тип управления может быть нулевым из-за метода сброса. Последний по умолчанию сбрасывает поле в null.

Если такое поведение не является желаемым, можно воспользоваться новым вариантом: nonNullable.
Этот параметр заменяет initialValueIsDefault, который становится устаревшим.

const nameControl = new FormControl('', { nonNullable: true });
Войдите в полноэкранный режим Выход из полноэкранного режима

В предыдущем примере типизация не является явной, здесь она неявная. Angular автоматически поймет, что значение имеет тип string и не является null.

FormGroup

Как и класс FormControl, класс FormGroup также наследует generic, который может быть неявным, но также и явным.

const addressControl = new FormGroup({
  street: new FormControl('', { nonNullable: true }),
  city: new FormControl('', { nonNullable: true }),
});

const street: addressControl.value.street // string|undefined
Войдите в полноэкранный режим Выход из полноэкранного режима

Как тип значения уличной переменной может быть неопределенным?

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

Простой способ обойти это поведение — использовать метод getRawValue, который возвращает все значения формы независимо от ее состояния.

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

Именно в этих случаях явный ввод действительно важен и интуитивно понятен.

interface PersonForm {
  firstname: FormControl<string>;
  lastname: FormControl<string>;
  username?: FormControl<string | null>
}

const personForm = new FormGroup<PersonForm>({
  firstname: new FormControl('', { nonNullable: true }),
  lastname: new FormControl('', { nonNullable: true }),
  username: new FormControl(null),
});

personForm.removeControl('firstname'); // error: firstname required

personForm.removeControl('username'); // no error
Войдите в полноэкранный режим Выход из полноэкранного режима

FormRecord

Типирование имеет много преимуществ, но оно может иметь и некоторые незначительные недостатки.

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

Angular добавляет новый API для решения этой проблемы, FormRecord

const languages = new FormRecord({
  french: new FormControl(false, { nonNullable: true }),
  english: new FormControl(false, { nonNullable: true })
});

languages.addControl('italian', new FormControl(0, { nonNullable: true }); // error

languages.addControl('italian', new FormControl(false, { nonNullable: true }); // no error
Войдите в полноэкранный режим Выход из полноэкранного режима

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

В отличие от аналога FormGroup, методы setValue и removeControl не имеют проверки типов.

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

FormArray

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

const names = new FormArray([new FormControl('', { nonNullable: true })])
Войдите в полноэкранный режим Выход из полноэкранного режима

Таким образом, каждый элемент управления в массиве FormArray будет иметь тип FormControl.

И снова явная типизация возможна, если неявной типизации недостаточно.

const names = new FormArray<FormControl<string>>([new FormControl('', { nonNullable: true })])
Войдите в полноэкранный режим Выход из полноэкранного режима

FormBuilder

Подобно приведенным выше примерам, класс FormBuilder был обновлен для поддержки типизации.

В дополнение к этому обновлению доступна новая инъекция _ NonNullableFormBuilder_, позволяющая избежать шаблонов, навязываемых опцией ‘nonNullable’.

@Component({
  selector: 'app-form',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  addressForm = this.fb.group({
    street: '',
    city: ''
  });

  constructor(private readonly fb: NonNullableFormBuilder) {}
}
Войдите в полноэкранный режим Выход из полноэкранного режима

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

@Component({
  selector: 'app-form',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  addressForm = this.fb.nonNullable.group({
    street: '',
    city: ''
  });

  constructor(private readonly fb: FormBuilder) {}
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Миграция

При переходе на Angular 14 схема миграции внесет небольшие изменения в ваш код, чтобы преобразовать все ваши классы FormGroup, FormControl и FormArray в их соответствующие «UnTyped» версии.

Вот преобразования, которые будут выполнены:

FormControl —> UnTypedFormControl
FormGroup —> UnTypedFormGroup
FormArray —> UnTypedFormArray

К сведению: UnTypedFormControl — это просто псевдоним для FormControl

Напоминаю: эта миграция будет выполнена в следующей команде:

ng update @angular/core
Войдите в полноэкранный режим Выход из полноэкранного режима

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

ng update @angular/core --migrate-only=migration-v14-typed-forms
Войдите в полноэкранный режим Выход из полноэкранного режима

Небольшой бонус и советы

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


interface Person {
  name: string;
  username: string;
}

type ControlsFromInterface<T extends Record<string, any> = {
  [key in keyof T]: T[key] extends Record<any, any>
    ? FormGroup<ControlsFromInterface<T[key]>>
    : FormControl<T[key]>
};

const personForm = new FormGroup<ControlsFromInterface<Person>>({
  name: new FormControl('', { nonNullable: true }),
  username: new FormControl('', { nonNullable: true })
});

Войдите в полноэкранный режим Выход из полноэкранного режима

Оцените статью
Procodings.ru
Добавить комментарий