Типизированные формы Angular — это очень здорово. Сильная типизация форм дает много преимуществ, но одна проблема сдерживает ее: возможность определить единый интерфейс, на основе которого можно создать типизированную форму и вывести из нее значение формы.
Рассмотрим следующий код:
interface UserFormControls {
firstName: FormControl<string>;
lastName: FormControl<string>;
email: FormControl<string | null>;
age: FormControl<number | null>;
}
interface User {
firstName: string;
lastName: string;
email: string | null;
age: number | null;
}
function processUser(user: User): void {
// ...
}
const userForm = new FormGroup<UserFormControls>({
firstName: new FormControl('foo', { nonNullable: true }),
lastName: new FormControl('bar', { nonNullable: true }),
email: new FormControl('foo@bar.com', { nonNullable: true }),
age: new FormControl(null)
});
processUser(userForm.value); // This won't actually compile, keep reading
В идеале, вы не хотите быть вынуждены поддерживать два отдельных интерфейса, определяющих одно и то же. Пользовательский интерфейс может быть выведен из UserFormControls, так что давайте сделаем это. Для этого мы используем два новых типа.
type FormValue<T extends AbstractControl> =
T extends AbstractControl<infer TValue, any>
? TValue
: never;
type FormRawValue<T extends AbstractControl> =
T extends AbstractControl<any, infer TRawValue>
? TRawValue
: never;
Давайте посмотрим, что произойдет, когда мы применим их к нашим UserFormControls
.
interface UserFormControls {
firstName: FormControl<string>;
lastName: FormControl<string>;
email: FormControl<string | null>;
age: FormControl<number | null>;
}
type UserForm = FormGroup<UserFormControls>;
type User = FormValue<UserForm>;
// type User = {
// firstName?: string | undefined;
// lastName?: string | undefined;
// email?: string | null | undefined;
// age?: number | null | undefined;
// }
type UserRaw = FormRawValue<UserForm>;
// type UserRaw = {
// firstName: string;
// lastName: string;
// email: string | null;
// age: number | null;
// }
Обратите внимание, что тип User
теперь имеет все свойства как необязательные. Это сделано потому, что элементы управления могут быть отключены, и они не будут отображаться в окончательном значении формы. Необработанное значение набирается точно так же, как мы указывали интерфейс User ранее. Именно поэтому processUser(userForm.value);
в первом блоке кода не будет компилироваться.
Сделайте свой выбор
Здесь вы должны сделать выбор:
- Вы можете либо использовать
FormValue<...>
и иметь дело с каждым свойством, которое потенциальнонеопределено
, либо; - Использовать
FormRawValue<...>
с осторожностью. Если все элементы управления, которые могут быть отключены, помечены как необязательные, ваш ввод будет правильным.
Я бы рекомендовал последнее. В этом случае мы получим следующее решение:
type User = FormRawValue<UserForm>;
// type User = {
// firstName: string;
// lastName: string;
// email: string | null;
// age: number | null;
// }
// ...
function processUser(user: User): void {
// ...
}
processUser(userForm.value as User);
// or:
processUser(userForm.getRawValue());
Удачи!