Angular 2(八): 表单

标准HTML表单

  • 相关检测属性:required、partern、min、max
  • 一般写法
    1
    2
    3
    <form action="/register" method="post">
    <input type="text"/>
    </form>

在Angular中,有两种形式的表单:响应式表单、模板式表单

模板式表单:表单的数据模型是通过组件中的指令隐式创建的。使用这种方式定义表单的数据模型时,受限于HTML语法,模板驱动方式仅适用于一些简单的场景。

响应式表单:通过编写ts代码而不是HTML来创建底层数据模型。在模型创建之后,可以使用一些特定的指令将模板的HTML元素与底层数据模型连接在一起。

模板式表单

在Angular中,标准表单会被自动托管,常规的操作无效。如果希望使用原始表单,只需要在form标签内加上ngNoForm指令即可;

1
<form action="/register" method="post" ngNoForm></form>

知识梳理

  • #myForm=”ngForm”获取表单对象,myForm.value获取表单的值,取值形式:

  • (ngSubmit)=”onSubmit(myform.value)”代替了默认的submit

  • 只能在HTML中操作数据模型,不能在ts中操作

  • 在ngForm上使用双向绑定时,不需要像以往那样[(ngModel)]=”user.name”,改写为:直接在标签中加入ngModel指令,同时指定name属性:name=”username”

  • 如果要在ngmodel上使用组件模板变量,#username=”ngModel”,取值形式:

  • ngForm=>隐式创建FormGroup

  • ngModel=>隐式创建FormControl

  • ngModelGroup=>隐式创建FormGroup(嵌入结构,用于将一些字段更好地组织在一起,加入指定的对象中)

模板式表单的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<form #myForm="ngForm" (ngSubmit)="onSubmit(myForm.value)">
<div>
姓名
<input type="text" #username="ngModel" ngModel name="username"/>
</div>
<div ngModelGroup="psw">
<div>
密码
<input type="text" #psw_1="ngModel" ngModel name="psw_1"/>
</div>
<div>
密码
<input type="text" #psw_2="ngModel" ngModel name="psw_2"/>
</div>
</div>
<button type="submit">提交</button>
</form>

1
2
3
onSubmit(val:any):void{
console.log(val); //查看表单的值
}

响应式表单

  • 响应式表单不可以使用模板变量,如:#myForm,只能在ts中操作数据模型

  • FormControl

    1
    2
    3
    4
    5
    6
    7
    8
    userName:FormControll=new FormControl();
    //可以指定初始值,userName:FormControll=new FormControl('Default');
    //常规使用
    <input [formControl]="username"/>
    //如果放在FormGroup中,需要改变写法
    <input formControlName="username"/>
  • FormGroup

    1
    2
    3
    4
    5
    6
    //构造函数需要一个对象
    //一般用于创建一组固定的子集,使用key检索
    formModel:FormGroup = new FormGroup({
    from: new FormControl(),
    to: new FormControl(),
    });
  • FormArray:创建一个可变长度子集

    1
    2
    3
    4
    5
    6
    //构造函数需要一个数组
    //使用索引值检索
    emails: FormArray = new FormArray([
    new FormControl(),
    new FormControl(),
    ])

响应式表单的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//注意(submit)和(ngSubmit)写法的区别
<form [formGroup]="formModel" (submit)="onSubmit()">
//通过formControlName将底层数据模型和HTML元素绑定在一起
//formControl在FormGroup中的写法
<input type="text" formControlName="username">
//使用formGroupName将特定的属性存储在指定(dateRange)的对象中
<div formGroupName="dateRange">
//FormGroup的实例对象通过key(from、to)检索元素
开始日期:<input type="date" formControlName="from"/>
结束日期:<input type="date" formControlName="to"/>
</div>
<div>
//绑定到FormArray的实例对象
<ul formArrayName="emails">
//由于FormArray是可变长度,一般与ngFor搭配使用
//this.formModel.get('emails')获取的是一个new FormArray对象,通过.controls取到内部元素集合
<li *ngFor="let email of this.formModel.get('emails').controls;let i = index;">
//由于FormArray使用的是索引值检索,故使用索引值作为模板的名字
//formControlName在此处为输入属性
邮箱:<input type="email" [formControlName]="i"/>
</li>
</ul>
<div (click)="addEmail()">增加一个</div>
</div>
<button type="submit">保存</button>
</form>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//在ts中建立底层数据模型
formModel:FormGroup = new FormGroup({
//formControl在FormGroup中的写法
username:new FormControl(),
dateRange:new FormGroup({
from: new FormControl(),
to: new FormControl(),
}),
emails:new FormArray([
new FormControl(),
])
});
addEmail():void{
//通过this.formModel.get('emails')获取的是特定的FormArray的实例对象(类型为对象),需要转换成FormArray类型
var emails = this.formModel.get('emails') as FormArray;
emails.push(new FormControl());
}
onSubmit():void{
//获取表单的值
console.log(this.formModel.value);
}

特别介绍

数据类型:FormGroup、FormControl、FormArray

指令:formGroup、formControl

指令:formGroupName、formControlName、formArrayName

  • formGroup和formGroupName用来绑定FormGroup
  • formControl和formControlName用来绑定FormControl
  • formArrayName用来绑定FormArray

Angular表单进阶

借助Angular内部工具FormBuilder改建表单

1
2
3
4
5
6
7
8
9
10
11
formModel: FormGroup;
constructor(public fb:FormBuilder){
this.formModel = fb.group({
username:[''],
mobile:[''],
passwords:fb.group({
psw_1:[''],
psw_2:[''],
})
})
}

表单校验

  • 标准格式
    1
    2
    3
    nameValidator(control:AbstractControl):{[key:string]:any}{
    return null;
    }

从标准格式可以看出,校验之后返回的对象为null或者一个key为字符串的对象

  • Angular提供的校验器:Validators.required、Validators.minLength(len)等等
    1
    2
    3
    4
    //使用自带校验器对username字段进行校验
    //在数据模型中(formModel)
    //校验器以数组的形式出现
    username: ['', [ Validator.required,Validator.minLength(6)] ]
1
2
3
4
onSubmit():viod{
let isValid:boolean = this.formModel.get('username').valid;
let error:any = this.formModel.get('username').error;
}
  • 自定义校验器

对于单独的FormControl检验

1
2
3
4
5
6
7
mobileValidator(control:FormControl):any{
let reg = /^[0-9]*$/;
let valid = reg.test(control.value);
//当校验通过时,返回null,否则返回一个空对象
return valid ? null : { mobile:true }
}

  • 注意校验器放置的位置
    1
    mobile:['', this.mobileValidator]

对于放在FormGroup内部的FormControl的校验

1
2
3
4
5
6
7
8
equalPswValidator(group:FormGroup):any{
let psw_1:FormControl = group.get('psw_1') as FormControl;
let psw_2:FormControl = group.get('psw_2') as FormControl;
let valid = ( psw_1.value === psw_2.value );
return valid ? null : { equalPsw:{msg:'我是错误提示信息!'} }
}

  • 注意校验器放置的位置和写法:{validator:this.equalPswValidator}
    1
    2
    3
    4
    passwords:fb.group({
    psw_1:[''],
    psw_2:[''],
    },{validator:this.equalPswValidator})

异步校验器:

通过代码模拟http请求处理

1
2
//只需要修改return语句即可,返回的是一个可观察对象
return Observable.of(valid? null:{mobile:{msg:'错误信息!'}}}).delay(3000);

1
mobile:['',mobileValidator,asyncMobileValidator]

通过校验器可以做到的额外的事

  • 通过校验器方法来控制组件中的提示语句隐藏或者显示:hasError、getError
  • 获取独立的FormControl的校验结果:formModel.hasError(‘mobile’,’mobile’);
  • 获取存在FormGroup内部的FormControl的校验结果:formModel.hasError(‘minLength’,[‘passwords’,’psw_1’])
  • 获取独立的FormControl校验反馈的信息:formModel.getError(‘mobile’,’mobile’);
  • ④获取存在FormGroup内部的FormControl校验反馈的信息:formModel.getError(‘equalPsw’,’passwords’) ? msg:null;

注意方法的第一个参数不是校验器的名称,而是校验器返回来的对象的key,即:

1
2
return valid ? null : { equalPsw:true } 中的 equalPsw
return valid ? null : { mobile:true } 中的mobile

在④中,如果发生错误,那么将会获取到返回的“错误提示信息”。

完整的代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<form [formGroup]="formModel" (submit)="onSubmit()">
<div>
用户名:<input type="text" formControlName="username"/>
<div [hidden]="!formModel.hasError('minlength','username')">
用户名输入不正确
</div>
</div>
<div>
手机号:<input type="text" formControlName="mobile"/>
<div [hidden]="!formModel.hasError('mobile','mobile')">
<div>{{ formModel.getError('mobile','mobile')?.msg }}</div>
</div>
</div>
<div formGroupName="passwords">
<div>
密码:<input type="text" formControlName="psw_1"/>
<div [hidden]="!formModel.hasError('minlength',['passwords','psw_1'])">
密码最小长度不符合要求
</div>
</div>
<div>
确认密码:<input type="text" formControlName="psw_2"/>
<div [hidden]="!formModel.hasError('equalPsw','passwords')">
{{ formModel.getError('equalPsw','passwords')?.msg }}
</div>
</div>
</div>
<button type="submit">提交</button>
</form>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
formModel:FormGroup;
constructor(fb:FormBuilder){
this.formModel = fb.group({
username:['',[Validators.required,Validators.minLength(6)]],
mobile:['',this.mobileValidator], //发现一个现象,如果此处加上Validators.required,程序会报错
passwords:fb.group({
psw_1:['',Validators.minLength(8)],
psw_2:[''],
},{validator:this.equalPswValidator})
})
}
mobileValidator(control:FormControl):any{
let reg = /^[0-9]*$/;
let valid = reg.test(control.value);
return valid ? null : { mobile:{msg:'手机号码不正确!'} }
}
equalPswValidator(control:FormGroup):any{
let psw_1 = control.get('psw_1') as FormControl;
let psw_2 = control.get('psw_2') as FormControl;
let valid = (psw_1.value === psw_2.value);
return valid ? null : { equalPsw:{msg:'密码错误提示信息!'}}
}
onSubmit(){
let valid = this.formModel.valid;
console.log(valid);
};

状态字段

  • touched/untouched:字段是否获取过焦点,如果获取过则:true/false,否则相反;

    1
    2
    3
    <div [hidden]="!formModel.get('mobile').valid || formModel.get('mobile').untouched">
    当获得过焦点,且表单校验为false,那么显示这段错误提示信息!
    </div>
  • pristine/dirty:如果一个值未被修改过,则:true/false,否则相反;

    1
    2
    3
    <div [hidden]="formModel.get('mobile').valid || formModel.get('mobile').pristine">
    当值被修改过,且不合法的情况下显示该提示信息!
    </div>
  • pending:当一个字段正处于异步校验中时,该字段为true