9. angular表单
9.1.1 表单概述
模板式表单
表单的数据模型是通过组件模板中的相关指令来定义的,因为使用这种方式定义表单的数据模型时,我们会受限与HTML的语法,所以模板驱动方式只适用于一些简单的场景
响应式表单
使用响应式表单时,你通过编写TypeScript代码而不是Html代码来创建一个底层的数据模型,在这个模型定义好以后,你使用一些特定的指令,将模板上的html元素与底层的数据模型连接起来
不管是那种表单,都有一个对应的数据模型来储存表单的数据。在模板式表单中,数据模型是由angular基于你组件模板中的指令隐式创建的。而在响应式表单中,你是通过编码明确的创建数据模型然后将模板上的html元素与底层的数据模型连接起来。
数据模型并不是一个任意的对象,他是一个由angular/forms模块中的而一些特定的类,如FormControl,FormGroup,FormArray等组成的。在模板式表单中,你是不能直接访问到这些类的。
9.1.2 html表单
<!-- 这是一个简单的html表单 -->
<form action="/register">
<!-- input属性
1. readonly: 只读
2. required: 必填。提交表单的时候会进行校验
3. pattern: 规定输入字段的模式、格式。值为正则表达式
4. min/max: 规定输入字段的最小值/最大值
5. maxlength: 规定输入字段的最大长度
5. formnovalidate:如果有该属性,则表单提交的时候不进行校验
注意:html自带表单校验的提示语是无法修改的
-->
<div><label for="">姓名:</label> <input type="text" readonly /></div>
<div><label for="">手机号:</label> <input type="text" formnovalidate pattern="[Asd]{1,4}" /></div>
<div><label for="">密码:</label> <input type="password" /></div>
<div><label for="">确认密码:</label> <input type="password" /></div>
<button type="submit">注册</button>
</form>
缺点:
-
不能实现数据的双向绑定
-
数据校验的方式以及校验提示太过简单
-
不能控制数据提交的格式
9.1.3 模板式表单
模板表单的几个重要属性
ngForm
-
angular项目中的每一个form标签都会被自动挂载这个属性
-
这个属性会拦截form表单的默认提交行为,并且隐式的创建一个formGroup类。同时会自动找到表单中标有ngModel属性的表单元素,并将其(name属性)添加到formGroup类中,记录表单元素的值。
<form #myForm="ngForm" action="/register">
<div><label for="">姓名:</label> <input type="text" ngModel name="username" /></div>
<div><label for="">手机号:</label> <input type="text" /></div>
<div><label for="">密码:</label> <input type="password" /></div>
<div><label for="">确认密码:</label> <input type="password" /></div>
<button type="submit">注册</button>
</form>
<div>
{{ myForm.value | json }}
</div>
上面的代码中只有
姓名
字段带有ngModel标记,所以最终myForm对象中只有userName一个属性(ngModel的具体使用请看下面)
#myForm
是我自己定义的模板变量,用来代表ngForm如果form表单使用ngNoForm,项目中的这个表单就不会被angular接管,保持表单的原有特性。
当显示的声明ngForm属性的时候,form标签可以换成其他标签。
ngModel
ngModel会隐式的创建一个formControl,并存储表单元素的值。
作用一:实现数据的双向绑定
<input type="text" [(ngModel)]="name"/>
作用二: 将表单元素添加到表单数据模型的一部分,此时表单元素必须要有name属性--通过myForm获取表单元素的时候对应的键。
<!--form表单同时有双向绑定数据-->
<div><label for="">姓名:</label> <input type="text" [(ngModel)]="userForm.userName" name="username" /></div>
<!--form表单同时没有双向绑定数据-->
<div><label for="">姓名:</label> <input type="text" ngModel name="username" /></div>
通过#userName="ngModel"
定义模板变量替代ngModel。这样就可以在模板中通过userName.value
访问姓名输入框的值了。
<div><label for="">姓名:</label> <input type="text" #userName="ngModel" ngModel name="username" /></div>
ngModelGroup
功能有点类似于ngForm。会创建一个formGroup对象。表现在ngForm对象中创建一个子对象
<form #myForm="ngForm" action="/register">
<div><label for="">姓名:</label> <input type="text" [(ngModel)]="userForm.userName" name="username" /></div>
<!---通过 ngModelGroup="phoneInfo" 创建一个名为phoneInfo的modelGroup-->
<div #myPhone="ngModelGroup" ngModelGroup="phoneInfo">
<label for="">手机号:</label> <input type="text" ngModel name="phone" />
</div>
</form>
<div>
{{ myForm.value | json }} <br>
{{ myPhone.value | json }}
</div>
每个表单元素的
#模板变量名="ngModel"是互不影响的
模板式表单是不可以直接操作FormControl这些类的,而是通过ngForm这些属性来操控的。
9.1.4 响应式表单
通过以下三个核心类创建表单数据模型;然后通过指令将数据模型和模板中的表单元素对应起来。
-
在组件中导入表单类,创建数据模型
import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, FormArray } from '@angular/forms'
...
formModel: FormGroup = new FormGroup({ // FormGroup代表整个表单数据模板
startTime1: new FormControl(''),
endTime1: new FormControl(),
timeRang: new FormGroup({ // FormGroup代表表单的一部分数据模板
startTime: new FormControl(''),
endTime: new FormControl()
}),
})
// 添加邮件
addEmail() {
// 通过这种方式添加电子邮件个数,a
this.formModel.get('emails').push(new FormControl('aaa@xxx.com.cn'))
}页面里面连接表单元素
<form action="/register" [formGroup]="formModel">
<div><label for="">初始时间: </label><input type="date" formControlName="startTime1" /></div>
<div><label for="">结束时间: </label><input type="date" formControlName="endTime1"/></div>
<div formGroupName="timeRang"> <!--用对应的FormGroup的名称将这部分内容包裹-->
<div><label for="">初始时间: </label><input type="date" formControlName="startTime" /></div>
<div><label for="">结束时间: </label><input type="date" formControlName="endTime"/></div>
</div>
<ul formArrayName="emails"><!--带有formArray属性的元素要包裹对应动态集合中所有formControl对应的元素-->
<li *ngFor="let email of formModel.get('emails').controls;let i=index">
<label for="">邮件{{i+1}}: </label><input type="text" [formControlName]="i" /> </li>
</ul>
<button type="submit" (click)="addEmail()">add email</button>
<button type="submit" (click)="onSubmit()">提 交</button>
</form>FormGroup: 多个
FormControl
的集合。可以是整个表单,也可以是表单里面的一个集合。分别对应上面代码中的formModel
和timeRang
FormArray: 可以动态增加或者减少的多个
FormControl
集合。通过
formModel.value.emails
可以获取邮件列表的值。通过formModel.get('emails')
方法可以获取邮件的FormArray对象。FormControl: 表单数据模型的基本结构。用于存放表单元素的值、校验信息以及是否被修改过。
-
通过指令将html元素连接到数据模型上
formGroup: 绑定对应的集合名称(属性绑定的形式).指定当前表单与哪个数据模型绑定
formGroupName: 绑定对应的集合名称(非属性绑定的形式)。
formArrayName:绑定对应的动态可增减集合名称。(非属性绑定的形式)
formControlName: 将对应的formControl与单个表单元素对应起来(非属性绑定的形式)。执行表单元素绑定的control字段
formControl: 将对应的formControl与单个表单元素对应起来。只能绑定单个零散的FormControl,而不能绑定某个form集合中的FormControl(属性绑定的形式)
-
构造方法传参:FormGroup传参为对象;FormArray: 传参是数据项为FormControl类型对象的数组;FormControl传参是字符串或不传参。
-
js修改和查看表单各元素值的方法:查看值:this.formModel.value;获取formGroup集合中的某个对象:this.formModel.get();更改值:具体请查看接angular官方文档对应上面三个类的属性描述。
-
响应式表单不可以通过模板变量访问值,只能通过模板数据及其方法访问;模板式表单可以通过模板变量访问和操作,但是不能直接通过formGroup这些接口操作。
FormBuilder:
FormBuilder
提供了一个语法糖,以简化FormControl
、FormGroup
或FormArray
实例的创建过程。 它会减少构建复杂表单时所需的样板代码的数量。使用FormBuilder简化上面的数据模板的创建过程:
...
formModel:any
// 声明一个fb变量,为FormBuilder类型
constructor(public fb: FormBuilder) {
// 构建一个新的 FormGroup 实例
this.formModel = fb.group({
timeRang: fb.group({
startTime: fb.control(''),
endTime: fb.control('')
}),
emails: fb.array([
fb.control('aaa@xx.com.cn')
])
})
}
// 添加邮件
addEmail() {
this.formModel.get('emails').push(this.fb.control('aaa@xx.com.cn'))
}
...FormBuilder的control、group、array方法除了可以分别创建出对应的
FormControl
、FormGroup
或FormArray
实例外,还可以接收其它参数提供更多功能。具体请看官网。 -
9.1.5 表单校验
angular的表单验证包括内置表单验证)和自定义表单验证。
-
每当表单控件中的值发生变化时,Angular 就会进行验证。只会校验被修改的控件。
-
angular的内置校验器都在Validators类中,通过
Validators.checkName
的形式访问。-
与表单校验有关的关键属性:
valid
、inValid
、errors
、status
等
-
-
angular的内置验证其可以适合一些简单的验证,复杂的表单验证建议使用自定义表单验证-还可以自定义提示信息。
-
自定义验证器的传参类型可以是:
AbstructControl
、FormControl
和FormArray
三种类型。某个控件或者子集合的校验不通过,那他的包裹集合的验证(valid)属性也是不通过的
9.1.5.1 响应式表单验证
<h2 style="color: brown">响应式表单校验</h2>
<form [formGroup]="formModel">
<div><label for="">姓名:</label><input type="text" required formControlName="userName" /></div>
<div><label for="">电话号码:</label><input type="text" formControlName="phone" /></div>
<div formGroupName="passwordGroup">
<div><label for="">密码:</label><input type="password" formControlName="password"/></div>
<div><label for="">确认密码:</label><input type="password" formControlName="confirmPass" /></div>
</div>
<button (click)="onSubmit()">提 交</button>
</form>
...
public formModel
// 验证单个表单空间的自定义验证器
// 参数类型
checkPhone(control: AbstractControl): any {
const phoneReg = /^1[3589]{1}\d{9}/
const valid = phoneReg.test(control.value)
console.log('phone', control)
return valid ? null : { tip: '电话号码格式有误' }
}
checkoutPassGroup(group: FormGroup): any {
console.log('确认密码校验', group)
// 直接获取值
// var valid = group.value.password === group.value.confirmPass ? null : { tip: '两次密码输入不一致'}
// 获取group中的control对象
var valid = group.get('password').value === group.get('confirmPass').value ? null : { tip: '两次密码输入不一致'}
return valid
}
constructor(public fb:FormBuilder) {
this.formModel = fb.group({
// 内置验证器,作为control的第二个参数,多个验证其的话第二个参数就是个数组
// 疑问:什么时候是函数,什么时候是属性;还有官网上这些方法传参类型怎么看不懂
userName: ['',[Validators.required, Validators.min(2)]],
// 自定义验证器
phone: ['', this.checkPhone],
passwordGroup: fb.group({
password: [''],
confirmPass: [''],
}, {validator: this.checkoutPassGroup})
})
}
...
-
上面
姓名
的校验是通过angular的内置校验器进行校验的。
通常类的实例化函数的第二个参数是要传入的构造器函数。如果内置校验器传参是类的实例化本身,可以写成简写形式,如上面电话号码的验证方法
this.checkPhone
和确认密码的方法{validator: this.checkoutPassGroup}
对于自定义校验器,如果想传入除去类实例本身以外的参数要怎么传一个类同时使用多个验证器的时候,用数组。如上面的姓名验证。
在响应式表单中,事实之源是其组件类。不应该通过模板上的属性来添加验证器,而应该在组件类中直接把验证器函数添加到表单控件模型上(
FormControl
)。然后,一旦控件发生了变化,Angular 就会调用这些函数
9.1.5.1.1 获取校验结果
通过状态字段
-
touched: 该表单元素是否已经获取过焦点。对于集合,只要其中一个元素获取过焦点,整个集合都是touched状态
-
untouched: 与
touched
相对的一个字段 -
pristine: 表单元素没有被修改过值的时候为true. 对于集合,只要其中一个元素获被修改过值,整个集合都是
dirty
状态。 -
dirty: 表单元素被修改过值。
-
pending: 字段处于校验过程中
-
hasError(): 可以判断某个表单元素的某个校验器有没有通过。返回值为布尔类型。
// 第一个参数是校验器名称(对于自定义校验器也适合),第二个参数是字段名称。
formModel.hasError('required', 'userName') -
自己获取字段的value,进行手动校验
-
conrol.setValue('value') : 设置表单元素的值
-
formModel.get('controlName'): 获取某个表单元素或者某个子集合
-
-