Зацініть @ng-stack/forms — модуль для строгої типізації Angular reactive forms

Хелоу! Скучили за мною? Знаю — скучили =). Ладно, перейду зразу до діла.

Хто писав на рідних реактивних ангуларовських формах, той знає на скільки вони погано типізовані, і через це інколи виникають помилки в коді, що важко дебажити.

Майже зразу після релізу продактної версії Angular (у вересні 2016), користувачі цього фреймворка закидали ангулар-розробників feature request’ами щоб вони зробили строгу типізацію реактивних форм. Пройшло більше двох років, але схоже що вони нічого і не планують покищо робити в цьому плані.

Як видно із вище наведеного issue, люди вже почали, так би мовити, пописувати свої рішення, але я переглянув штуки три-чотири — слабенько. Схоже, що у мене навички з TypeScript покращі за їхні. Зацініть самі.

Репозиторій, із моїм варіантом вирішення описаної вище проблеми, можна знайти на github: @ng-stack/forms.

Встановлювати стандартно:

npm i @ng-stack/forms
// OR
yarn add @ng-stack/forms

Даний модуль абсолютно нічого не змінює в плані runtime поведінки рідних ангуларовських методів. Кожен метод переписано приблизно наступним чином:

import { FormGroup as NativeFormGroup } from '@angular/forms';

export class FormGroup extends NativeFormGroup {
  get(path) {
    return super.get(path);
  }
}

Переписано лише типізацію, плюс — невеличкі правки по сигнатурі методів. Але усі зміни по сигнатурі зроблені виключно для кращого контролю даних, що вводять юзери цього фреймворка.

Однією із цікавих особливостей цього модуля є те, що він по моделі форми автоматично визначає тип для form control. Тобто не треба вже писати так:

get userName() {
  return this.formGroup.get('userName') as FormControl;
}

get addresses() {
  return this.formGroup.get('addresses') as FormGroup;
}

Можна вже писати так:

// Note here form model UserForm
formGroup: FormGroup<UserForm>;

get userName() {
  return this.formGroup.get('userName');
}

get addresses() {
  return this.formGroup.get('addresses');
}

Валідація

Класи `FormControl`, `FormGroup`, `FormArray` та усі методи класу `FormBuilder`
приймають у якості другого параметра для генеріка так звану «модель валідації»:

const control = new FormControl<string, { someErrorCode: true }>('some value');
control.getError('someErrorCode'); // OK
control.errors.someErrorCode // OK
control.getError('notExistingErrorCode'); // Error: Argument of type '"notExistingErrorCode"' is not assignable...
control.errors.notExistingErrorCode // Error: Property 'notExistingErrorCode' does not exist...

By default використовується спеціальний класс `ValidatorsModel`.

const control = new FormControl('some value');
control.getError('required'); // OK
control.getError('email'); // OK
control.errors.required // OK
control.errors.email // OK
control.getError('notExistingErrorCode'); // Error: Argument of type '"notExistingErrorCode"' is not assignable...
control.errors.notExistingErrorCode // Error: Property 'notExistingErrorCode' does not exist...

`ValidatorsModel` містить список властивостей, взятих із `typeof Validators`, а також типи, що повертаються:

class ValidatorsModel {
  min: { min: { min: number; actual: number } };
  max: { max: { max: number; actual: number } };
  required: { required: true };
  requiredTrue: { required: true };
  email: { email: true };
  minLength: { minlength: { requiredLength: number; actualLength: number } };
  maxLength: { requiredLength: number; actualLength: number };
  pattern: { requiredPattern: string; actualValue: string };
}
LinkedIn
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Додав підтримку для input[type=file].

Хоча я використовую «класичний» підхід з використанням FormData для завантаження файлів з браузера на сервер, але мій модуль має кілька плюсів:

  • Автоматична прив’язка назви поля, що йде на сервер, до атрибуту input name. Наприклад <input type="file" name="someName">, отже в formControl.value в переліку файлів буде назва поля someName, з яким файл приходитиме на сервер;
  • підтримка атрибуту multiple, тобто на сервер відправлятиметься стандртна назва поля з квадратними дужками (див. `userpic[]`)
    <input type="file" multiple name="userpic" [formControl]="formControl">
    
    formData.append('userpic[]', myFileInput.files[0]);
    formData.append('userpic[]', myFileInput.files[1]);
    
  • `Validators` має чотири статичні методи для перевірки файлів
    import { Validators } from '@ng-stack/forms';
    
    Validators.fileRequired;
    Validators.fileMaxSize(1024);
    Validators.filesMaxLength(10);
    Validators.filesMinLength(2);
    

Див. приклад на stackblitz.

Все ещё не погу понять плюсов Reactive Forms, как то не хочется работать с контролами в коде, просто биндинга темплейтов хватает. Может я что-то упускаю? Или сильно прикипел к концепции MVC)

Ось тут непогано і достатньо коротко описано плюси Angular Reactive Forms

Reactive forms use an explicit and immutable approach to managing the state of a form at a given point in time. Each change to the form state returns a new state, which maintains the integrity of the model between changes. Reactive forms are built around observable streams, where form inputs and values are provided as streams of input values, which can be accessed synchronously.

Reactive forms also provide a straightforward path to testing because you are assured that your data is consistent and predictable when requested. Any consumers of the streams have access to manipulate that data safely.

Reactive forms differ from template-driven forms in distinct ways. Reactive forms provide more predictability with synchronous access to the data model, immutability with observable operators, and change tracking through observable streams. If you prefer direct access to modify data in your template, template-driven forms are less explicit because they rely on directives embedded in the template, along with mutable data to track changes asynchronously. See the Forms Overview for detailed comparisons between the two paradigms.

Подписаться на комментарии