Angular 表单
表单
官方文档
Angular 提供了两种不同的方法来通过表单处理用户输入:响应式表单和模板驱动表单。 两者都从视图中捕获用户输入事件、验证用户输入、创建表单模型、修改数据模型,并提供跟踪这些更改的途径。
响应式表单:复用性更强。它们提供对底层表单 API 的直接访问,并且在视图和数据模型之间使用同步数据流,从而可以更轻松地创建大型表单。响应式表单测试很简单,测试时不需要深入理解变更检测,就能正确测试表单更新和验证。
模板驱动表单:专注于简单的场景,复用性不高。它们抽象出了底层表单 API,并且在视图和数据模型之间使用异步数据流。对模板驱动表单的这种抽象也会影响测试。测试较复杂,测试程序非常依赖于手动触发变更检测才能正常运行,并且需要进行更多设置工作。
响应式表单
使用表单控件有三个步骤:
- 要使用 formControl,需要在模块中引入 ReactiveFormsModule;
- 在组件类中创建 FormControl实例 name;
- 在组件模板中 input标签上 绑定 [formControl]=“name”;将模板和组件类关联起来。
获取表单控件的值
你可以用下列方式显示它的值:
- 通过可观察对象 valueChanges,你可以在模板中使用 AsyncPipe 或在组件类中使用 subscribe() 方法来监听表单值的变化。
- 使用 value 属性。它能让你获得当前值的一份快照。
修改表单控件的值
FormControl 提供了一个 setValue() 方法,它会修改这个表单控件的值,并且验证与控件结构相对应的值的结构。比如,当从后端 API 或服务接收到了表单数据时,可以通过 setValue() 方法来把原来的值替换为新的值。
- 组件中定义updateName方法,调用this.name.setValue设置新值;
- 模板中button绑定updateName方法;
响应式表单中的数据流
在响应式表单中,视图中的每个表单元素都直接链接到一个表单模型(FormControl 实例)。 从视图到模型的修改以及从模型到视图的修改都是同步的,而且不依赖于 UI 的渲染方式。
视图—>模型
这个视图到模型的示意图展示了当输入字段的值发生变化时数据是如何从视图开始,经过下列步骤进行的:
- 最终用户在输入框元素中键入了一个值,这里是 “Blue”;
- 这个输入框元素会发出一个带有最新值的 “input” 事件;
- 这个控件值访问器 ControlValueAccessor 会监听表单输入框元素上的事件,并立即把新值传给 FormControl 实例;
- FormControl 实例会通过 valueChanges 这个可观察对象发出这个新值;
- valueChanges 的任何一个订阅者都会收到这个新值。
模型—>视图
这个模型到视图的示意图体现了程序中对模型的修改是如何通过下列步骤传播到视图中的。
- favoriteColorControl.setValue() 方法被调用,它会更新这个 FormControl 的值。
- FormControl 实例会通过 valueChanges 这个可观察对象发出新值。
- valueChanges 的任何订阅者都会收到这个新值。
- 该表单输入框元素上的控件值访问器会把控件更新为这个新值。
把表单控件分组
表单中通常会包含几个相互关联的控件。响应式表单提供了两种把多个相关控件分组到同一个输入表单中的方法。
- 表单组定义了一个带有一组控件的表单,你可以把它们放在一起管理。你也可以通过嵌套表单组来创建更复杂的表单。
- 表单数组定义了一个动态表单,你可以在运行时添加和删除控件。你也可以通过嵌套表单数组来创建更复杂的表单。
就像 FormControl 的实例能让你控制单个输入框所对应的控件一样,FormGroup 的实例能跟踪一组 FormControl 实例(比如一个表单)的表单状态。当创建 FormGroup 时,其中的每个控件都会根据其名字进行跟踪。
下面的例子展示了如何管理单个控件组中的多个 FormControl 实例。
保存表单数据
FormGroup 指令会监听 form 元素发出的 submit 事件,并发出一个 ngSubmit 事件,让你可以绑定一个回调函数。
- 把 onSubmit() 回调方法添加为 form 标签上的 ngSubmit 事件监听器。
- 组件中的 onSubmit() 方法会捕获 profileForm 的当前值。如果要保持该表单的封装性,就要使用 EventEmitter 向组件外部提供该表单的值。下面的例子会使用 console.log 把这个值记录到浏览器的控制台中。
- form 标签所发出的 submit 事件是内置 DOM 事件,通过点击类型为 submit 的按钮可以触发本事件。这还让用户可以用回车键来提交填完的表单。往表单的底部添加一个 button,用于触发表单提交。
创建嵌套的表单组
表单组可以同时接受单个表单控件实例和其它表单组实例作为其子控件。这可以让复杂的表单模型更容易维护,并在逻辑上把它们分组到一起。
如果要构建复杂的表单,如果能在更小的分区中管理不同类别的信息就会更容易一些。使用嵌套的 FormGroup 可以让你把大型表单组织成一些稍小的、易管理的分组。
要制作更复杂的表单,请遵循如下步骤。
- 创建一个嵌套的表单组。
- 在模板中对这个嵌套表单分组。
更新部分数据模型
当修改包含多个 FormGroup 实例的值时,你可能只希望更新模型中的一部分,而不是完全替换掉。
有两种更新模型值的方式:
-
使用 setValue() 方法来为单个控件设置新值。 setValue() 方法会严格遵循表单组的结构,并整体性替换控件的值。setValue() 方法的严格检查可以帮助你捕获复杂表单嵌套中的错误,而 patchValue() 在遇到那些错误时可能会默默的失败。
-
使用 patchValue() 方法可以用对象中所定义的任何属性为表单模型进行替换。
使用 FormBuilder 服务生成控件
当需要与多个表单打交道时,手动创建多个表单控件实例会非常繁琐。FormBuilder 服务提供了一些便捷方法来生成表单控件。FormBuilder 在幕后也使用同样的方式来创建和返回这些实例,只是用起来更简单。
FormBuilder 通过下列步骤来使用。
- 导入 FormBuilder 类。
- 注入这个 FormBuilder 服务。
- 生成表单内容。
FormBuilder 服务有三个方法:control()、group() 和 array()。这些方法都是工厂方法,用于在组件类中分别生成 FormControl、FormGroup 和 FormArray。
用 group 方法来创建 profileForm 控件:
注意: 你可以只使用初始值来定义控件,但是如果你的控件还需要同步或异步验证器,那就在这个数组中的第二项和第三项提供同步和异步验证器。
表单验证
官方文档
在响应式表单中,在组件类中把验证器函数添加到表单控件模型上(FormControl)。然后,一旦控件发生了变化,Angular 就会调用这些函数。
验证器(Validator)函数
验证器函数可以是同步函数,也可以是异步函数。
同步验证器:这些同步函数接受一个控件实例,然后返回一组验证错误或 null。可以在实例化一个 FormControl 时把它作为构造函数的第二个参数传进去。
异步验证器 :这些异步函数接受一个控件实例并返回一个 Promise 或 Observable,它稍后会发出一组验证错误或 null。在实例化 FormControl 时,可以把它们作为第三个参数传入。
出于性能方面的考虑,只有在所有同步验证器都通过之后,Angular 才会运行异步验证器。当每一个异步验证器都执行完之后,才会设置这些验证错误。
内置验证器函数
定义自定义验证器
/* 自定义验证器函数:通过传入的正则表达式判断其是否满足条件,结果是ValidatorFn类型(key: error) */
export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const forbidden = nameRe.test(control.value);
return forbidden ? {
forbiddenName: {
value: control.value}} : null;
};
}
跨字段交叉验证器
跨字段交叉验证器是一种自定义验证器,可以对表单中不同字段的值进行比较,并针对它们的组合进行接受或拒绝。
下面的例子是常见的注册场景时,密码和确认密码是否一致的校验。
- 首先要将两个字段注册在一个group中,group的第二个参数设置{validators: 自定义交叉验证器};
- 自定义交叉验证器和普通的验证器函数格式一样,区别就是从control中获不同的表单组件,然后对比其值是否满足要求。
异步验证器
异步验证器实现了 AsyncValidatorFn 和 AsyncValidator 接口。它们与其同步版本非常相似,但有以下不同之处。
validate() 函数必须返回一个 Promise 或可观察对象,返回的可观察对象必须是有尽的,这意味着它必须在某个时刻完成(complete)。要把无尽的可观察对象转换成有尽的,可以在管道中加入过滤操作符,比如 first、last、take 或 takeUntil。
异步验证只有在同步验证成功时才会执行。异步验证开始之后,表单控件就会进入 pending 状态,可以检查控件的 pending 属性,并用它来给出对验证中的视觉反馈。一种常见的 UI 模式是在执行异步验证时显示 Spinner(转轮)。下面的例子展示了如何在模板驱动表单中实现这一点。
<input [(ngModel)]="name" #model="ngModel" appSomeAsyncValidator>
<app-spinner *ngIf</