# 类型化表单

从 Angular 14 开始,响应式表单默认是严格类型的。

# 前提条件

作为本指南的背景,你应该已经熟悉Angular 响应式表单。

# 类型化表单概览

使用 Angular 响应式表单,你可以显式指定表单 model。作为一个简单的例子,考虑这个基本的用户登录表单:

const login = new FormGroup({
    email: new FormControl(''),
    password: new FormControl(''),
});

Angular 提供了许多 API 来与此 FormGroup 交互。例如,你可以调用 login.valuelogin.controlslogin.patchValue 等。(有关完整的 API 参考,请参阅API 文档。)

在以前的 Angular 版本中,这些 API 中的大多数都在其类型中的某处包含 any,并且与控件结构或值本身的交互不是类型安全的。例如:你可以编写以下无效代码:

const emailDomain = login.value.email.domain;

使用严格类型的响应式表单时,上面的代码不会编译,因为 email 上并没有 domain 属性。

除了增加安全性之外,这些类型还支持各种其他改进,例如 IDE 中更好的自动完成,以及显式指定表单结构的方式。

这些改进当前仅适用于响应式表单(不适用于模板驱动的表单)。

# 自动无类型表单迁移

升级到 Angular 14 时,包含的迁移将自动使用相应的无类型版本替换代码中的所有表单类。例如,上面的代码段将变为:

const login = new UntypedFormGroup({
    email: new UntypedFormControl(''),
    password: new UntypedFormControl(''),
});

每个 Untyped 符号都与以前的 Angular 版本具有完全相同的语义,因此你的应用程序应该像以前一样继续编译。通过删除 Untyped 前缀,你可以增量启用这些类型。

# FormControl :入门

最简单的表单由单个控件组成:

const email = new FormControl('angularrox@gmail.com');

此控件将被自动推断为 FormControl 类型。TypeScript 会在整个FormControl API中自动强制执行此类型,例如 email.valueemail.valueChangesemail.setValue(...) 等。

# 可空性

你可能想知道:为什么此控件的类型包含 null ?这是因为控件可以随时通过调用 reset 变为 null

const email = new FormControl('angularrox@gmail.com');
email.reset();
console.log(email.value); // null

TypeScript 将强制你始终处理控件已变为 null 的可能性。如果要使此控件不可为空,可以用 nonNullable 选项。这将导致控件重置为其初始值,而不是 null

const email = new FormControl('angularrox@gmail.com', {nonNullable: true});
email.reset();
console.log(email.value); // angularrox@gmail.com

重申一下,此选项会在调用 .reset() 时影响表单的运行时行为,应小心翻转。

# 指定显式类型

可以指定类型,而不是依赖推理。考虑一个初始化为 null 的控件。因为初始值为 null,所以 TypeScript 将推断 FormControl,这比我们想要的要窄。

const email = new FormControl(null);
email.setValue('angularrox@gmail.com'); // Error!

为防止这种情况,我们将类型显式指定为 string|null

const email = new FormControl<string|null>(null);
email.setValue('angularrox@gmail.com');

# FormArray :动态的、同质的集合

FormArray 包含一个开放式控件列表。type 参数对应于每个内部控件的类型:

const names = new FormArray([new FormControl('Alex')]);
names.push(new FormControl('Jess'));

FormArray 将具有内部控件类型 FormControl

如果你想在数组中有多个不同的元素类型,则必须使用 UntypedFormArray,因为 TypeScript 无法推断哪种元素类型将出现在哪个位置。

# FormGroupFormRecord

Angular 为具有枚举键集的表单提供了 FormGroup 类型,并为开放式或动态组提供了一种名为 FormRecord 的类型。

# 部分值

再次考虑一个登录表单:

const login = new FormGroup({
    email: new FormControl('', {nonNullable: true}),
    password: new FormControl('', {nonNullable: true}),
});

在任何 FormGroup 上,都可以禁用控件。任何禁用的控件都不会出现在组的值中。

因此,login.value 的类型是 Partial<{email: string, password: string}&gt;。这种类型的 Partial 意味着每个成员可能是未定义的。

更具体地说,login.value.email 的类型是 string|undefined,TypeScript 将强制你处理可能 undefined 的值(如果你启用了 strictNullChecks)。

如果你想访问包括禁用控件的值,从而绕过可能的 undefined 字段,可以用 login.getRawValue()

# 可选控件和动态组

某些表单的控件可能存在也可能不存在,可以在运行时添加和删除。你可以用可选字段来表示这些控件:

interface LoginForm {
    email: FormControl<string>;
    password?: FormControl<string>;
}

const login = new FormGroup<LoginForm>({
    email: new FormControl('', {nonNullable: true}),
    password: new FormControl('', {nonNullable: true}),
});

login.removeControl('password');

在这个表单中,我们明确地指定了类型,这使我们可以将 password 控件设为可选的。TypeScript 会强制只有可选控件才能被添加或删除。

# FormRecord

某些 FormGroup 的用法不符合上述模式,因为键是无法提前知道的。FormRecord 类就是为这种情况设计的:

const addresses = new FormRecord<FormControl<string|null>>({});
addresses.addControl('Andrew', new FormControl('2340 Folsom St'));

任何 string|null 类型的控件都可以添加到此 FormRecord

如果你需要一个动态(开放式)和异构(控件是不同类型)的 FormGroup ,则无法提升为类型安全的,这时你应该使用 UntypedFormGroup

FormRecord 也可以用 FormBuilder 构建:

const addresses = fb.record({'Andrew': '2340 Folsom St'});

如果你需要一个动态(开放式)和异构(控件是不同类型)的 FormGroup,则无法提高类型安全,你应该使用 UntypedFormGroup

# FormBuilderNonNullableFormBuilder

FormBuilder 类已升级为支持新类型,方式与上面的示例相同。

此外,还有一个额外的构建器:NonNullableFormBuilder。它是在所有控件都上指定 {nonNullable: true} 的简写,用来在大型非空表单中消除主要的样板代码。你可以用 FormBuilder 上的 nonNullable 属性访问它:

const fb = new FormBuilder();
const login = fb.nonNullable.group({
    email: '',
    password: '',
});

在上面的示例中,两个内部控件都将不可为空(即将设置 nonNullable)。

你还可以用名称 NonNullableFormBuilder 注入它。

Last Updated: 5/16/2023, 7:35:10 PM