Привет, Хабр! Представляю вашему вниманию перевод статьи "Convert into Strongly Typed Angular Forms in a Minute" автора Ajay Ojha.
В данной статье мы узнаем о том, как превратить реактивную форму в строго типизированную без изменения определения класса.
Многие из нас любят TypeScript за его строгую типизацию. Это то, что делает фреймворк Angular таким мощным. При этом реактивные формы Angular не являются полностью строго типизированными. Все из-за очень частого использования ключевого слова «any», что негативно сказывается на качестве кода при разработке форм с точки зрения невозможности проверки на опечатки, несоответствие типов и т.д.
Недавно мой друг обсуждал задачи, связанные с возможностью реализации строгой типизации в реактивных формах в Angular. Я думал над тем же, и в результате пришел к интуитивно понятному решению, основанному на следовании принципу разделения интерфейсов. Но прежде чем углубиться в раздел «Как», давайте сперва взглянем на существующие задачи.
Исследование и задачи
Для любого крупномасштабного корпоративного frontend-приложения характерен постепенный рост, и появление в нем форм является неизбежным. Учитывая этот факт, мой друг проделал огромную работу по кастомизации реактивных форм, что позволило внедрить строгую типизацию, и как следствие, обнаруживать ошибки, возникающие из-за опечаток или неверного приведения типов еще на этапе сборки.
Вот несколько основных проблем:
- Во время сборки не возникает ошибка если есть опечатка в названии контрола;
- В valueChanges и statusChanges класса FormControl возникают ошибки приведения типов;
- Сложность управления вложенными FormGroup и FormArray из-за множества различных типов.
Все вышеперечисленные задачи были решены моим другом посредством расширения базовых классов (FormGroup, FormControl и FormArray), а также путем использования обобщенных классов. При этом проблемы все еще остаются, но немного в другом ключе:
- При таком подходе теряются все преимущества использования класса FormBuilder для создания FormControl, FormGroup и FormArray;
- Любая ошибка в обобщенных классах может привести к непредвиденному поведению всего приложения;
- Существует очень большая вероятность смешения классов, когда часть объектов FormGroup создаются через обобщенные классы, а часть посредством базового класса @angular/forms.
Самое время перейти к решению, которое устраняет все вышеперечисленные проблемы при этом ни на байт не увеличивая размер итогового бандла приложения.
Не использовать обобщенные классы? Как такое возможно?
Как я и сказал в начале, мы можем реализовать строгую типизацию реактивных форм применяя принцип разделения интерфейсов.
Как?
Для превращения наших реактивных форм в строго типизированные мы будем использовать @rxweb/types. Данный пакет содержит исключительно определения типов, поэтому можно не бояться, что что-то сломается в процессе выполнения.
Рассмотрим типичный случай, когда у нас имеются FormControl с вложенными FormGroup и FormArray. Ниже представлен код создания FormGroup.
export class AppComponent implements OnInit {
formGroup: FormGroup;
formBuilder: FormBuilder;
constructor(formBuilder: FormBuilder) {
this.formBuilder = formBuilder;
}
ngOnInit() {
this.formGroup = this.formBuilder.group({
firstName: ['', [Validators.required]],
address: this.formBuilder.group({
countryName: ["", Validators.required]
}),
skills: this.formBuilder.array([
this.formBuilder.group({
name: ["", Validators.required]
})
])
});
}
}
Теперь мы превратим данную форму в строго типизированную проделав четыре простых шага.
Шаг 1. Установка пакета
npm install @rxweb/types
Шаг 2. Создание интерфейса
Все вышеперечисленные имена контролов будут определены как свойства соответствующего интерфейса. Например:
User >> firstName, address, skills; Address >> countryName; Skill >> name.
Шаг 3. Импорт обобщенного интерфейса
Далее нам необходимо импортировать два указанных ниже интерфейса:
- IFormGroup: предоставляет строго типизированное API для FormGroup;
- IFormBuilder: дает возможность создать строго типизированные FormGroup, FormControl и FormArray.
Код импорта:
import { IFormGroup, IFormBuilder } from “@rxweb/types”;
Шаг 4. Превращаем реактивную форму в строго типизированную
Для преобразования мы должны выполнить следующие действия:
- Заменить тип FormGroup на IFormGroup;
- Заменить тип FormBuilder на IFormBuilder;
- Передать интерфейс соответствующему методу группы
group<User>
,group<Address>
иarray<Skill>
.
Вот, собственно, и все.
Код после преобразования:
formGroup: IformGroup<User>;
formBuilder: IformBuilder;
ngOnInit() {
this.formGroup = this.formBuilder.group<User>({
firstName: ['', [Validators.required]],
address: this.formBuilder.group<Address>({
countryName: ["", Validators.required]
}),
skills: this.formBuilder.array<Skill>([
this.formBuilder.group({
name: ["", Validators.required]
})
])
});
}
Таким образом, все вышеперечисленные проблемы решены путем выполнения четырех несложных шагов.
Ниже представлены ошибки приведения типов/опечаток, возникающие после преобразования формы в строго типизированную
Опечатка
![Опечатка](https://habrastorage.org/webt/rj/pu/2e/rjpu2ezcyfufehngwfu0rjmmfli.png)
Несоответствие типов
![Ошибка несоответствия типов](https://habrastorage.org/webt/cu/fm/ct/cufmctu3mkskhf0sorcezgne_x4.png)
Для более глубокого понимания работы API строго типизированных реактивных форм см. пример. Также для получения дополнительной информации вы можете обратиться к документации.
Заключение
Самое потрясающе то, что наша форма превратилась в строго типизированную без изменения определения класса, исключительно при помощи интерфейса. Но если вы ищете решение, которое предоставляет нечто большее, чем просто строгую типизацию, например, устраняет проблему дублирования кода, и при этом следует принципам предметно-ориентированного проектирования и принципу единственной ответственности, я бы посоветовал обратиться к статье «New Way to Validate the Angular Reactive Form».