响应式表单提供了一种模型驱动的方式来处理表单的输入。我们可以很方便的控制表单项的状态和验证器。响应式表单是围绕Observable
流构建的。流的消费者可以即方便又安全的操纵这些数据。
响应式表单语模板驱动的表单有着显著的不同点。响应式表单通过对数据模型的同步访问提供了更多的可预测性,使用Observable的操作符提供了不可变性,并通过Observable流提供了变化追踪功能。
模板驱动表单允许你直接在模板中修改数据,但不像响应式表单那么明确,因为响应式表单依赖嵌入模板的指令,并借助可变数据来异步跟踪变化。
使用响应式表单控件
要想模块中使用响应式表单,需要三步:
- 在应用模块中注册响应式表单模块。该模块声明了响应式表单的指令。
- 在组件中生成新的
FormControl
实例,确保可以在组件和组件的模板中访问到。 - 在模板中应用这个
FormControl
。
比如,我们有个AccountModule
,里面包含了一个登录组件,我们来使用响应式表单来完成这个登录表单。
在模块中注册响应式表单模块
首先是需要在AccountModule
里面注册响应式表单模块:
1 | import {NgModule} from '@angular/core'; |
在组件中使用FormControl
然后在LoginComponent
组件的ts中定义FormControl
:
1 | import {FormControl} from '@angular/forms'; |
在模板中应用该控件
为表单控件添加formControl
绑定,formControl
是由ReactiveFormsModule
中的FormControlDirective
提供的。
1 | <label for="name">登录名</label> |
在模板中板绑定后,就把我们声明的FormControl
表单控件注册给了模板中名为name
的input
输入元素。这样,表单控件和DOM元素就建立了通道,视图模板(view)会反映模型的变化,模型也会接收视图中的变动。
获得表单控件的值
有两种方式获得FormControl
的值:
- 可观察对象
valueChanges
。可以在模板中使用AsyncPipe
或者在组件类中订阅来监听表单的值。 - 使用value属性。可以获得控件的当前值的一份快照。
首先我们在模板中来获取值:
1 | name 的快照值为:{{name.value}} |
可以看到两者基本上是同步变化的。
这里额外说下AsyncPipe
。这个管道是从一个异步回执中获取值。async
管道会订阅一个Observable
或Promise
,并返回它发出的最近的一个值。当新值到来时,async
管道就会把该组件标记为需要进行变更检测。当组件被销毁时,async
管道就会自动取消订阅,以消除潜在的内存泄露问题。具体可以查看angular async pipe。
然后我们再看看在组件类中以订阅的方式获得控件值变更的方式:
1 | this.name.valueChanges.subscribe(res => { |
运行的时候可以看到,这里的输出和页面上基本保持一致的。这个方式的意义在于,我们可以监听字段值的变化,以便于在输入的时候去控制它。可以看看这篇以前写的限制input输入的文章。
更新表单控件的值
FormControl
提供了setValue
方法,可以在组件类中动态修改控件的值,而不依赖交互的输入。
我们先在组件类中添加一个方法,使用setValue
修改name的值:
1 | updateName() { |
然后在模板中使用这个方法:
1 | <button (click)="updateName()">修改名字</button> |
可以看到,当点击后模板中的所有显示的地方都变化为修改后的值,而且我们的组件类中的监听函数也收到了值的变更。
当然,FormControl
也提供了patchValue
方法来修补控件的值,但它在FormControl
层面上和setValue
没啥区别,这个函数只在FormGroup
和FormArray
上具有不同于setValue
的行为。
禁用/解除禁用表单控件
在表单中这是个很常见的场景了。我们展示表单的时候,希望某个表单控件是被禁用的,无法输入的。
一般做法是直接在模板上给表单控件加上disabled
属性。这个在模板驱动表单的时候是可以的,但是在使用响应式表单的时候直接加disabled
的话会出现个提示:
1 | It looks like you're using the disabled attribute with a reactive form directive..... |
这个警告是说,你既然使用了响应式表单,那么最好用响应式表单的方式来禁用,不要使用模板的那一套了。
那么我们怎么通过响应式表单的方式来禁用呢?
禁用表单控件
我们在初始化表单控件的时候,是直接使用了new FormControl(null)
这样创建的,来看看FormControl
的初始化参数:
formState
使用初始值或定义了初始值和禁用状态的对象初始化控件。可选,默认是nullvalidatorOrOpts
同步验证器函数或验证器数组。或者包含验证函数和验证触发器的对象。可选。asyncValidator
异步验证器或验证器数组。可选。
我们可以这样在初始化的时候赋值和禁用表单控件:
1 | export class LoginComponent { |
运行可以看到,表单控件是已经禁用的状态,也无法输入。
当然,这个禁用是禁用交互输入,在组件类中使用setValue
还是可以变更值的。
PS:需要特别注意,当formState
是对象的时候,那么必须要包含两个属性:value
和disabled
,否则就会当成控件的初始值来显示,模板中会显示[object, Object]
。
不仅仅是在初始化的时候禁用,我们可以在初始化之后,通过FormControl
的disable
函数来动态禁用:
1 | ngOnInit() { |
解除禁用
既然能禁用,那么也可以解除禁用,FormControl
提供了disable
和enable
两组,来禁用和解除禁用。
我们在组件类中提供两个方法来禁用和解除禁用:
1 | disableName() { |
在模板中消费:
1 | <button (click)="disableName()">禁用 name</button> |
真是如丝般滑~ 😎
我们可以读取控件的禁用状态,然后用一个按钮综合起来:
1 | nameState() { |
在模板中消费:
1 | <button (click)="nameState()"> |
FormControl
是响应式表单的基石,先搞清楚这个是基本。后续的FormGroup
和FormArray
的组成对象都是FormControl
。先夯实基础,然后再有上层建筑。