一、模版驱动
表单的控制逻辑写在组件模版中,比较适合简单的表单类型。
(一)创建一个简单的表单
- 需要在组件类中引入
FormsModule
,并且定义一个提交的方法
import {
Component} from '@angular/core';
import {
FormsModule} from "@angular/forms";
@Component({
selector: 'app-layout',
templateUrl: './layout.component.html',
})
export class LayoutComponent {
onSubmit(value: any) {
console.log(value);
}
}
- 在组件模版中,通过
#myForm="ngForm"
将这个表单定义为Angular
表单,这样就可以使用Angular
表单中提供的特性。对这个表单绑定提交的事件。对于表单控件,需要设置name
属性作为唯一索引,设置ngModel
属性实现与表单对象的双向数据绑定。
<form #myForm="ngForm" (submit)="onSubmit(myForm)">
<input type="text" name="username" ngModel />
<button type="submit">Submit</button>
</form>
- 在网页中,输入一段文字,点击提交按钮,查看提交事件接收到的
value
是一个ngForm
对象,里面的form.value
就是控件双向绑定的数据源。表单控件的name
属性就作为数据源的键,value
作为值。只有设置了ngModel
的控件,输入值才会被记录到form.value
中
(二)表单分组
当需要的表单项比较多时,可以分组对表单项进行管理。
- 组件模版中,通过
ngModelFroup
进行分组,分组的名字会作为直属属性名存储到表单对象中。
这里将用于分组的元素换为ng-container
,就不会渲染成真实的元素
<form #myForm="ngForm" (submit)="onSubmit(myForm)">
<h3>用户信息</h3>
<div ngModelGroup="user">
名字:<input type="text" name="username" ngModel />
年龄:<input type="number" name="age" ngModel />
</div>
<h3>车辆注册信息</h3>
<div ngModelGroup="car">
品牌:<input type="text" name="brand" ngModel />
颜色:<input type="color" name="color" ngModel />
</div>
<button type="submit">Submit</button>
</form>
- 填写表单,点击提交查看控制台
form.value
:
(三)表单验证
Angular
表单提供的几个常见的验证规则:
- required 必填字段
- minlength 最小长度
- maxlength 最大长度
- pattern 正则验证
- 组件模版中,在需要验证的表单项上加上需要验证的规则
名字:<input type="text" name="username" ngModel pattern="\d" maxlength="10"/>
年龄:<input type="number" name="age" ngModel required/>
- 组件类中,在提交的时候,可以通过
form.valid
属性(Boolean)查看表单验证是否通过
onSubmit(form: any) {
console.log(form.valid);
}
- 对于提交按钮,可以限制当form.valid为false时禁止点击。
!myForm.valid
可以写成myForm.invalid
<button type="submit" [disabled]="!myForm.valid">Submit</button>
- 验证不通过时,可以在
HTML
中增加提示信息
需要单个表单项显示自己的提示信息的时候,就必须先拿到单独的表单项,通过#username=’ngModel‘
获取该表单项;
username
的类型是NgMdel
,根据touched
属性获取该表单项是否点击或者输入;
touched
属性当input
框有鼠标移入并且移出后会变为true
;
invalid
属性获取该表单项验证是否不通过;
errors
属性存储所有不通过的验证项的信息。
名字:<input #username='ngModel' type="text" required name="username" pattern="\d*" ngModel maxlength="10"/>
<button type="submit">Submit</button>
<div *ngIf="username.touched && username.invalid && username.errors" style="color: red">
<div *ngIf="username.errors['required']">用户名必填</div>
<div *ngIf="username.errors['pattern']">用户名必须是数字</div>
<div *ngIf="username.errors['maxlength']">用户名最大长度为10</div>
</div>
二、模型驱动
表单的控制逻辑写在组件类中,对于表单的验证等操作更加灵活,比较适合复杂的表单。
模型驱动表单是一个FormGroup
类的实例,它可以对表单整体进行验证;其中的每一个表单项都是一个FormControl
类的实例,可以对单个表单项进行验证以及可以监测表单值的修改。
(一)创建表单
- 在组件所属的模块中,需要引入
ReactiveFormsModule
模块,并且添加到imports
数组中
import {
ReactiveFormsModule} from "@angular/forms";
@NgModule({
...
imports: [
...
ReactiveFormsModule
],
...
})
- 在组件类中,需要引入需要的模块,并且将当前表单定义为
FormGroup
类的实例,将每一个表单项定义为FormControl
类的实例
...
import {
FormGroup, FormControl} from "@angular/forms";
...
export class LayoutComponent {
public form: FormGroup = new FormGroup({
username: new FormControl(),
age: new FormControl(),
});
onSubmit() {
console.log(this.form);
}
}
- 在组件模版中,当前表单使用
formGroup
属性绑定组件类中定义的FormGroup
实例,表单项使用formControllName
绑定每一个FormControl
实例;formGroup
绑定的是一个对象,所以需要使用中括号;formControllName
绑定的是字符串,所以不需要中括号。
<form [formGroup]="form" (submit)="onSubmit()">
名字:<input type="text" formControlName="username"/>
年龄:<input type="number" formControlName="age"/>
<button type="submit">Submit</button>
</form>
- 输入表单项,点击提交,看一下输出的
this.form
的数据结构
(二)表单分组
- 组件类中,在
FormGroup
对象内部创建FormGroup
对象就可以实现表单分组
public form: FormGroup = new FormGroup({
user: new FormGroup({
username: new FormControl(),
age: new FormControl(),
}),
car: new FormControl()
});
- 组件模版中,分组使用
formGroupName
绑定分组的名称
<form [formGroup]="form" (submit)="onSubmit()">
<ng-container formGroupName="user">
名字:<input type="text" formControlName="username"/>
年龄:<input type="number" formControlName="age"/>
</ng-container>
车辆:<input type="text" formControlName="car"/>
<button type="submit">Submit</button>
</form>
(三)动态创建表单:FormArray
FormArray
用于动态添加一组表单项。整个表单form
需要绑定一个FormGroup
实例对象,这个实例对象内部可以放置FormControl
对象(单个表单项)、FormGroup
对象(一组表单项),也可以放置FormArray
对象(动态添加的一组表单项)。
- 组件类中,在当前表单绑定的
FormGroup
实例对象里面,创建一个属性,类型为FormArray
,里面的每一个元素都是一个FormGroup
。初始化放一个元素。
public carForm: FormGroup = new FormGroup({
cars: new FormArray([
new FormGroup({
name: new FormControl(),
power: new FormControl()
})
])
});
- 组件模版中
- 通过
formArrayName
绑定这个FormArray
对象的属性名,就可以实现模版和FormArray
对象数据的双向数据绑定。 FormArray
实例的controls
属性,是一个数组,保存动态添加的表单组的数据。由于是一个数组,所以渲染的时候,需要通过*ngFor
进行绑定。controls
的每一项,都是一个FormGroup
实例对象,所以对于每一项,都需要使用FormGroupName
绑定当前元素的index
属性- 每一个
FormGroup
实例对象,里面的表单项,都是一个FormControl
实例对象,所以需要使用FormControlName
绑定属性名
<form [formGroup]="carForm" (submit)="onSubmit()">
<div formArrayName="cars">
<div *ngFor="let car of cars.controls; let i = index" [formGroupName]="i">
<input formControlName="name" placeholder="Car name">
<input formControlName="power" placeholder="Car power">
</div>
</div>
</form>
FormArray
适用于动态添加表单组,所以,我们需要一个按钮,触发添加表单组的操作。以及,向FormArray
对象里添加一个元素的方法。FormArray
对象的push
方法可以实现向FormArray
对象中添加一个元素。
<button (click)="addCar()">增加一组表单</button>
addCar() {
const cars = this.carForm.get('cars') as FormArray;
cars.push(new FormGroup({
name: new FormControl(),
power: new FormControl()
}));
}
- 对于
cars
这个变量,每次添加表单的时候,都需要获取一遍,所以可以将cars
,定义为一个get
形式的变量。这样就可以直接通过this.cars
访问cars
这个FromArray
实例对象。
get cars(): FormArray {
return this.carForm.get('cars') as FormArray;
}
- 当需要删除动态添加进来的表单组时,在组件模版中,每一个动态添加的
FormGroup
对象都需要一个删除按钮,触发删除操作,并将当前FormGroup
对象的index
传递过去;在组件类中,需要调用FromArray
对象的removeAt
方法,删除指定索引的formGroup
对象。
<div *ngFor="let car of cars.controls; let i = index" [formGroupName]="i">
<input formControlName="name" placeholder="Car name">
<input formControlName="power" placeholder="Car power">
<button (click)="removeCar(i)">Remove</button>
</div>
removeCar(i: number) {
// 从cars中删除第i个元素。比数组删除元素方便
this.cars.removeAt(i);
}
- 增加
submit
事件,提交时打印当前表单的value
(三)表单验证
- 内置验证器
在组件类中需要引入内置验证器;使用new
关键字创建FormControl
对象时,第二个参数传递一个验证规则组成的数组
并且将username
定义为get
类型的变量方便获取。
import {
FormGroup, FormControl,Validators} from "@angular/forms";
public carForm: FormGroup = new FormGroup({
username: new FormControl('', [
Validators.required,
Validators.minLength(4)])
})
get username() {
return this.carForm.get('username') as FormControl;
}
组件模版中,可以通过username
的几个属性,判断这几个验证规则是否通过;可以通过carForm.valid
判断表单中所有的验证规则是否都通过了。
<form [formGroup]="carForm" (submit)="onSubmit()">
<input type="text" formControlName="username">
<div *ngIf="username.touched && username.invalid && username.errors">
<div *ngIf="username.errors['required']">Username is required</div>
<div *ngIf="username.errors['minlength']">Username must be at least 3 characters long</div>
</div>
<button type="submit" [disabled]="carForm.invalid">Submit</button>
</form>
(四)自定义表单验证器
自定义验证器有如下几条规则
- 自定义验证器是一个
TypeScript
类 - 类中包含具体的验证方法,验证方法必须为静态方式,使用
static
修饰 - 验证方法接受一个参数
control
,类型为AbstractControl
,就是FormControl
类的实例对象的类型。 - 如果验证成功,返回
null
- 如果验证失败,返回一个对象,
key
为验证标识,value
为true
,表示该项验证失败
(1)自定义同步表单验证器
1. 定义同步验证器
- 验证器是一个
ts
类,所以要定义到一个.ts文件里面 - 接收的参数类型是
AbstractControl
- 返回的参数类型是
ValidationErrors | null
- 方法名和标识名保持一致
- 这个类需要被组件引入,所以需要使用
export
导出
import {
AbstractControl, ValidationErrors} from "@angular/forms";
export class MyValidators {
static connotContainSpace(
control: AbstractControl
): ValidationErrors | null {
if (/\s/.test(control.value)) {
return {
connotContainSpace: true}
}
return null;
}
}
2. 使用同步验证器
- 在组件类中引入自定义验证器
import {
MyValidators} from "./MyValidators";
- 在表单项new的时候,将需要该表单项需要使用的验证规则传递进去。直接写定义,不需要调用。
public carForm: FormGroup = new FormGroup({
username: new FormControl('', [
Validators.required,
Validators.minLength(4),
MyValidators.connotContainSpace])
})
- 在组件模版中提示
<div *ngIf="username.touched && username.invalid && username.errors">
<div *ngIf="username.errors['required']">Username is required</div>
<div *ngIf="username.errors['minlength']">Username must be at least 3 characters long</div>
<div *ngIf="username.errors['connotContainSpace']">用户名不能包含空格</div>
</div>
(2)自定义异步表单验证器
1. 定义异步验证器
- 异步验证器返回值的类型是
Promise
或Observable
,这个Promise
resolve的结果是Validators | null
- 只有所有的同步验证都通过后才会执行异步验证,如果同步验证存在不通过的情况,则不会执行异步验证
// 异步验证器
static shouldBeUnique(
control: AbstractControl
): Promise<ValidationErrors | null> {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (control.value === 'mosh') {
resolve({
shouldBeUnique: true})
} else {
resolve(null)
}
}, 2000)
})
}
2. 使用异步验证器
- 异步验证器要放在
formControl
对象创建时的第三个参数的位置 - 在模版中的使用与内置验证器相同
public carForm: FormGroup = new FormGroup({
username: new FormControl('', [
Validators.required,
Validators.minLength(5),
MyValidators.connotContainSpace],
MyValidators.shouldBeUnique)
})
3. 显示验证中状态
formControl
对象有一个属性pending
,异步验证执行过程中,该属性为true
,否则为false
<div *ngIf="username.pending">正在验证...</div>
(五)FormBuilder
FormBuilder
是一个类,这个类的实例对象可以帮助我们快速创建表单- 创建
FormGroup
对象可以使用formBuilder.group()
方法,这个方法接收两个参数,第一个参数是controls
,即表单项组成的对象,第二个参数是options
,即对于formGroup
对象的校验规则;写在这里的校验规则通常是自定义校验规则,接收formGroup
作为参数,可以定义对于整个表单里面的所有表单项的验证规则。比如两个表单项不能相同等等。 - 创建
FormControl
对象可以使用formBuilder.control()
方法,这个方法可以接受三个参数,第一个参数是表单项的默认值,第二个参数是同步表单验证器组成的数组,第三个参数是异步表单验证器组成的数组 - 创建
FormControl
对象还可以直接将一个数组作为表单项的value
,数组元素列表同上述formBuilder.control()
方法的参数列表
constructor(private formBuilder: FormBuilder) {
}
public form:FormGroup = this.formBuilder.group({
username: this.formBuilder.control(''),
password: ['', [Validators.required, Validators.minLength(6)]],
})
(六)模型驱动表单常用方法
(1)patchValue
- 设置表单控件的值,可以设置全部,也可以设置一个
onPatchValue() {
this.form.patchValue({
username: 'mosh'
})
}
(2)setValue
- 设置全部表单控件的值。不设置全部就不会起作用。
onSetValue() {
this.form.setValue({
username: 'meggie',
password: '123456'
})
}
(3)valueChanges
- 当表单控件的值发生变化时触发的事件。
- 需要获取到某一个想要监听变化的表单控件,然后注册监听函数
- 订阅函数
subscribe
中获取到的参数就是修改后的值
ngOnInit() {
this.form.get('username')?.valueChanges.subscribe(value => {
console.log(value);
})
}
(4)reset
- 表单内容置空
onReset() {
this.form.reset();
}