0%

Angular 响应式表单之 FormControl

响应式表单提供了一种模型驱动的方式来处理表单的输入。我们可以很方便的控制表单项的状态和验证器。响应式表单是围绕Observable流构建的。流的消费者可以即方便又安全的操纵这些数据。

响应式表单语模板驱动的表单有着显著的不同点。响应式表单通过对数据模型的同步访问提供了更多的可预测性,使用Observable的操作符提供了不可变性,并通过Observable流提供了变化追踪功能。

模板驱动表单允许你直接在模板中修改数据,但不像响应式表单那么明确,因为响应式表单依赖嵌入模板的指令,并借助可变数据来异步跟踪变化。

使用响应式表单控件

要想模块中使用响应式表单,需要三步:

  1. 在应用模块中注册响应式表单模块。该模块声明了响应式表单的指令。
  2. 在组件中生成新的FormControl实例,确保可以在组件和组件的模板中访问到。
  3. 在模板中应用这个FormControl

比如,我们有个AccountModule,里面包含了一个登录组件,我们来使用响应式表单来完成这个登录表单。

在模块中注册响应式表单模块

首先是需要在AccountModule里面注册响应式表单模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import { LoginComponent } from './login/login.component';
import {ReactiveFormsModule} from "@angular/forms";

@NgModule({
declarations: [LoginComponent],
imports: [
CommonModule,
ReactiveFormsModule,
]
})
export class AccountModule {
}

在组件中使用FormControl

然后在LoginComponent组件的ts中定义FormControl

1
2
3
4
5
6
7
8
9
import {FormControl} from '@angular/forms';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent {
name: FormControl = new FormControl(null);
}

在模板中应用该控件

为表单控件添加formControl绑定,formControl是由ReactiveFormsModule中的FormControlDirective提供的。

1
2
<label for="name">登录名</label>
<input id="name" type="text" [formControl]="name"/>

在模板中板绑定后,就把我们声明的FormControl表单控件注册给了模板中名为nameinput输入元素。这样,表单控件和DOM元素就建立了通道,视图模板(view)会反映模型的变化,模型也会接收视图中的变动。

获得表单控件的值

有两种方式获得FormControl的值:

  • 可观察对象valueChanges。可以在模板中使用AsyncPipe或者在组件类中订阅来监听表单的值。
  • 使用value属性。可以获得控件的当前值的一份快照。

首先我们在模板中来获取值:

1
2
name 的快照值为:{{name.value}}
name 实时监听值: {{name.valueChanges | async}}

可以看到两者基本上是同步变化的。

这里额外说下AsyncPipe。这个管道是从一个异步回执中获取值。async管道会订阅一个ObservablePromise,并返回它发出的最近的一个值。当新值到来时,async管道就会把该组件标记为需要进行变更检测。当组件被销毁时,async管道就会自动取消订阅,以消除潜在的内存泄露问题。具体可以查看angular async pipe

然后我们再看看在组件类中以订阅的方式获得控件值变更的方式:

1
2
3
this.name.valueChanges.subscribe(res => {
console.log(res);
});

运行的时候可以看到,这里的输出和页面上基本保持一致的。这个方式的意义在于,我们可以监听字段值的变化,以便于在输入的时候去控制它。可以看看这篇以前写的限制input输入的文章

更新表单控件的值

FormControl提供了setValue方法,可以在组件类中动态修改控件的值,而不依赖交互的输入。

我们先在组件类中添加一个方法,使用setValue修改name的值:

1
2
3
updateName() {
this.name.setValue('tony');
}

然后在模板中使用这个方法:

1
<button (click)="updateName()">修改名字</button>

可以看到,当点击后模板中的所有显示的地方都变化为修改后的值,而且我们的组件类中的监听函数也收到了值的变更。

当然,FormControl也提供了patchValue方法来修补控件的值,但它在FormControl层面上和setValue没啥区别,这个函数只在FormGroupFormArray上具有不同于setValue的行为。

禁用/解除禁用表单控件

在表单中这是个很常见的场景了。我们展示表单的时候,希望某个表单控件是被禁用的,无法输入的。

一般做法是直接在模板上给表单控件加上disabled属性。这个在模板驱动表单的时候是可以的,但是在使用响应式表单的时候直接加disabled的话会出现个提示:

1
It looks like you're using the disabled attribute with a reactive form directive.....

这个警告是说,你既然使用了响应式表单,那么最好用响应式表单的方式来禁用,不要使用模板的那一套了。

那么我们怎么通过响应式表单的方式来禁用呢?

禁用表单控件

我们在初始化表单控件的时候,是直接使用了new FormControl(null)这样创建的,来看看FormControl的初始化参数:

  • formState 使用初始值或定义了初始值和禁用状态的对象初始化控件。可选,默认是null
  • validatorOrOpts 同步验证器函数或验证器数组。或者包含验证函数和验证触发器的对象。可选。
  • asyncValidator 异步验证器或验证器数组。可选。

我们可以这样在初始化的时候赋值和禁用表单控件:

1
2
3
4
5
6
export class LoginComponent {
name: FormControl = new FormControl({
value: 'tony',
disabled: true,
});
}

运行可以看到,表单控件是已经禁用的状态,也无法输入。

当然,这个禁用是禁用交互输入,在组件类中使用setValue还是可以变更值的。

PS:需要特别注意,当formState是对象的时候,那么必须要包含两个属性:valuedisabled,否则就会当成控件的初始值来显示,模板中会显示[object, Object]

不仅仅是在初始化的时候禁用,我们可以在初始化之后,通过FormControldisable函数来动态禁用:

1
2
3
ngOnInit() {
this.name.disable();
}

解除禁用

既然能禁用,那么也可以解除禁用,FormControl提供了disableenable两组,来禁用和解除禁用。

我们在组件类中提供两个方法来禁用和解除禁用:

1
2
3
4
5
6
disableName() {
this.name.disable();
}
enableName() {
this.name.enable();
}

在模板中消费:

1
2
<button (click)="disableName()">禁用 name</button>
<button (click)="enableName()">解除禁用name</button>

真是如丝般滑~ 😎

我们可以读取控件的禁用状态,然后用一个按钮综合起来:

1
2
3
4
5
6
7
nameState() {
if (this.name.disabled) {
this.name.enable();
} else {
this.name.disable();
}
}

在模板中消费:

1
2
3
4
<button (click)="nameState()">
{{name.disabled ? '启用' : '禁用'}}
name
</button>

FormControl是响应式表单的基石,先搞清楚这个是基本。后续的FormGroupFormArray的组成对象都是FormControl。先夯实基础,然后再有上层建筑。

码字辛苦,打赏个咖啡☕️可好?💘