# 类型化表单
从 Angular 14 开始,响应式表单默认是严格类型的。
# 前提条件
作为本指南的背景,你应该已经熟悉Angular 响应式表单。
# 类型化表单概览
使用 Angular 响应式表单,你可以显式指定表单 model。作为一个简单的例子,考虑这个基本的用户登录表单:
const login = new FormGroup({
email: new FormControl(''),
password: new FormControl(''),
});
Angular 提供了许多 API 来与此 FormGroup
交互。例如,你可以调用 login.value
、 login.controls
、 login.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.value
、 email.valueChanges
、 email.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 无法推断哪种元素类型将出现在哪个位置。
# FormGroup
和 FormRecord
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}>
。这种类型的 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
。
# FormBuilder
和 NonNullableFormBuilder
FormBuilder
类已升级为支持新类型,方式与上面的示例相同。
此外,还有一个额外的构建器:NonNullableFormBuilder
。它是在所有控件都上指定 {nonNullable: true}
的简写,用来在大型非空表单中消除主要的样板代码。你可以用 FormBuilder
上的 nonNullable
属性访问它:
const fb = new FormBuilder();
const login = fb.nonNullable.group({
email: '',
password: '',
});
在上面的示例中,两个内部控件都将不可为空(即将设置 nonNullable
)。
你还可以用名称 NonNullableFormBuilder
注入它。