说明
看到百度上没有特别好的formgroup详解,这里翻译一下,顺便记录下自己的理解
内容
这里直接就搬官网的说明了:
FormGroup 每个子 FormControl
的值聚合到一个对象中,以每个控件名称作为键。它通过减少其子项的状态值来计算其状态。例如,如果组中的某个控件无效,则整个组都将变得无效。FormGroup 是用于在 Angular 中定义表单的四个基本构建块之一,与 FormControl、FormArray 和
FormRecord 。实例化 FormGroup 时,请传入子控件的集合作为第一个参数。每个子项的键都会注册控件的名称。
FormGroup 旨在用于提前已知键的用例。如果需要动态添加和删除控件,请改用 FormRecord 。
FormGroup 接受一个可选的类型参数 TControl ,它是一种以内部控件类型作为值的对象类型
构造函数
new FormGroup('默认值',同步验证器,异步验证器)
属性
属性是继承 AbstracControl,下面翻译一下:(这里要说明一下,这里翻译的,都是针对formgroup的,因为对于formArray,formControl可能是不同的)
属性 | 说明 |
---|---|
value: TValue | 控件组的键值对 |
validator: ValidatorFn | null | 返回用于同步确定此控件的有效性的函数 |
asyncValidator: AsyncValidatorFn | null | 返回用于异步确定此控件的有效性的函数 |
parent:FormGroup | FormArray | null | 父控件 |
status | 控件的验证状态 |
valid:boolean | 当 status 为 VALID 时 |
invalid: boolean | 当 status 为 INVALID 时 |
pending: boolean | 当 status 为 PENDING 时 |
disabled: boolean | 当控件的 status 为 DISABLED 时,禁用的控件免于验证检查,并且不包含在其祖先控件的汇总值中 |
enabled:boolean | 同disabled |
errors:ValidationErrors | null | 包含因验证失败生成的任何错误的对象,如果没有错误,则为 null |
pristine: boolean | 如果用户尚未更改 UI 中的值,则控件是 pristine 的 |
dirty: boolean | 如果用户更改了 UI 中的值,则控件是 dirty 的 |
touched: boolean | 如果控件被标记为 touched ,则为真。一旦用户在其上触发了 blur 事件,则控件就会被标记为已 touched |
untouched: boolean | touched的相反用法 |
valueChanges: Observable | 一种多播 observable,每次控件的值发生更改(在 UI 中或以编程方式)时都会发出事件。它还会在你每次调用 enable() 或 disable() 时发出一个事件,而不将 {emitEvent: false} 作为函数参数传递 |
statusChanges: Observable | 每次重新计算控件的验证 status 时都会发出事件的多播 observable。 |
updateOn: FormHooks | 报告 AbstractControl 的更新策略(意味着控件更新本身的事件)。可能的值: ‘change’ |
root: AbstractControl | 检索此控件的顶级祖先 |
这里有几个比较难理解的,进行说明一下
1 disabled/enabled
这里需要注意的是,当我们disabled某个控件的时候,在提交表单时,不会进行验证。
这里密码输入框为DISABLED,在返回的value里面是没有age属性的。并且其状态是DISABLED.
2 valueChanges/statusChanges
这两个用法差不多,这里就讲下valueChanges的用法。这个主要是用来监视表单值变化的,是一个订阅。具体用法如下
这里呢,在构造函数里,用valueChanges进行数据监控,当name里的数值发生变化时,会订阅这个回调函数。返回的是所有的数据。如果监听单一的数据变化怎么写呢?如下:
this.validForm.get('name')?.valueChanges.subscribe(data=>{
debugger
})
3 updateOn
这个总共有3个默认值’change’ | ‘blur’ | ‘submit’;什么意思呢。就是规定表单的值什么时候进行验证。我们基本都是在输入的时候进行校验,所以默认是change,如果想在点击提交按钮时验证的话,就改成submit。具体写法如下:
方法
属性 | 说明 |
---|---|
registerControl() | 使用组的控件列表注册控件 |
addControl() | 向此组添加控件 |
removeControl() | 从此组中删除控件 |
setControl() | 替换现有的控件 |
contains() | 检查组中是否存在具有给定名称的启用控件 |
setValue() | 它接受一个与组结构匹配的对象,以控件名称作为键 |
patchValue() | 它接受以控件名称作为键的对象,并尽力将值与组中的正确控件进行匹配 |
reset() | 将所有后代标记为 pristine 和 untouched ,并将所有后代的值设置为默认值,如果没有提供默认值,则为 null |
getRawValue | FormGroup 的聚合值,包括任何禁用的控件 |
这里举几个例子说明下,用法基本差不多
1 patchValue
html的写法
TS的写法
这里梳理下思路:在界面上定义了一组表单,其中年龄这个控件,在初始化时,对其数据进行了监控。那么我们用patchValue对部分数据
进行修改时,可以设置emitEvent属性。当为true时,则在数值改变时,会触发这个订阅,即会进入到valueChanges的回调中。如果设置为false,则不会进入。
这里把其父类的方法也总结下:
方法 | 说明 |
---|---|
setValidators() | 设置此控件上处于活动状态的同步验证器。调用此方法会覆盖任何现有的同步验证器 |
setAsyncValidators() | 设置此控件上处于活动状态的异步验证器。调用此方法会覆盖任何现有的异步验证器。 |
addValidators() | 向此控件添加一个或多个同步验证器,而不影响其他验证器。 |
addAsyncValidators() | 向此控件添加一个或多个异步验证器,而不影响其他验证器。在运行时添加或删除验证器时,你必须调用 updateValueAndValidity() 以使新验证生效。添加已存在的验证器将没有任何效果。 |
removeValidators() | 从此控件中删除同步验证器,而不影响其他验证器。 |
removeAsyncValidators() | 从此控件中删除异步验证器,而不影响其他验证器。 |
hasValidator() | 检查此控件上是否存在同步验证器函数。 |
hasAsyncValidator() | 检查此控件上是否存在异步验证器函数。 |
clearValidators() | 清空同步验证器列表。 |
clearAsyncValidators() | 清空异步验证器列表。 |
markAsTouched() | 将控件标记为 touched 。不更改值的焦点和模糊事件会触及控件。 |
markAllAsTouched() | 将控件及其所有后代控件标记为 touched 。 |
markAsUntouched() | 将控件标记为 untouched |
markAsDirty() | 将控件标记为 dirty 。当通过 UI 更改控件的值时,控件会变脏; |
markAsPristine() | 将控件标记为 pristine 。 |
markAsPending() | 将控件标记为 pending 。 |
disable() | 禁用控件。这意味着此控件免于验证检查,并从任何父级的聚合值中排除。 |
enable() | 同disable |
setParent() | 设置控件的父级 |
setValue() | 设置控件的值。抽象方法(在子类中实现) |
patchValue() | 修补控件的值。抽象方法(在子类中实现)。 |
reset() | 重置控件。抽象方法(在子类中实现)。 |
getRawValue() | 此控件的原始值。对于大多数控件实现,原始值将包括禁用的子项。 |
updateValueAndValidity() | 重新计算控件的值和验证状态。 |
setErrors() | 手动而不是自动运行验证时,在表单控件上设置错误。 |
get() | |
getError() | 报告具有给定路径的控件的错误数据。 |
hasError() | 报告具有给定路径的控件是否具有指定的错误。 |
推论
关于表单验证,一套完整的复杂的表单验证,其实有很多需要注意的。这里我列举一个在企业中,实际产生的操作。
需求:
我们给一个学校做一个操作系统,其中有一个需求,就是根据班级,筛选出其中的班级总人数,男女人数。这个学校的10班是个特殊班级,只有女生。
我们的弹出框:
功能描述:
1 点击添加时,弹出添加弹窗。均可编辑
2 勾选时,弹出编辑弹窗,当选择10班时,隐藏男生条目。并且女生条目不可编辑。
这里稍微用ng-zorro-antd框架美化一下
代码:
这里分两种思维方式去写代码
第一种:
html
<div style="border-bottom: solid 1px rgb(165, 153, 153)">
<button nz-button style="margin: 10px" nzType="primary">添加</button>
<button nz-button style="margin: 10px" nzType="default">编辑</button>
</div>
<nz-table #basicTable [nzData]="listOfData">
<thead>
<tr>
<th>班级</th>
<th>总人数</th>
<th>男生人数</th>
<th>女生人数</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let data of basicTable.data">
<td>{{ data.class }}</td>
<td>{{ data.total }}</td>
<td>{{ data.man }}</td>
<td>{{ data.woman }}</td>
<td>
<a><i nz-icon nzType="edit" nzTheme="outline"></i></a>
<a><i nz-icon nzType="delete" nzTheme="outline"></i></a>
</td>
</tr>
</tbody>
</nz-table>
其实添加都很简单,初始化表单之后,点击确定,就可以调用ajax把数据传到后台了。那么编辑怎么做呢?其实,编辑要做的就是回显以及初始化数据之后的联动。
TS
add(){
this.isVisible=true;
}
edit(){
this.isVisible=true;
}
isVisible:boolean=false;
//取消
handleCancel(){
this.isVisible = false
}
//确定
submit() {
this.isVisible = false
}
validForm!: FormGroup;
constructor(private fb: FormBuilder){}
ngOnInit(): void {
this.initForm();
}
initForm() {
this.validForm = this.fb.group({
class: ['three', [Validators.required]],
total: ['', [Validators.required]],
man: ['', [Validators.required]],
woman: ['', [Validators.required]],
});
}
listOfData = [
{
key: '1',
class: '一班',
total:50,
man: 32,
woman: 18
},
{
key: '4',
class: '四班',
total:52,
man: 32,
woman: 20
},
{
key: '3',
class: '三班',
total:26,
man: 15,
woman: 11
}
];
list = [
{ name: '一班', value: 'one' },
{ name: '二班', value: 'two' },
{ name: '三班', value: 'three' },
{ name: '四班', value: 'four' },
{ name: '五班', value: 'five' },
{ name: '六班', value: 'six' },
{ name: '七班', value: 'seven' },
{ name: '八班', value: 'eight' },
{ name: '九班', value: 'nine' },
{ name: '十班', value: 'ten' },
];
clsChange(e: any) {
//当班级为ten时,隐藏男生条目,并且女生条目不可编辑
if (e == 'ten') {
this.validForm.get('man')?.disable()
this.validForm.get('woman')?.disable()
}else{
this.validForm.get('man')?.enable()
this.validForm.get('woman')?.enable()
}
}
我们已经在clsChange()方法中对表单进行了启用以及禁用的逻辑判断。那么,当我们点击编辑按钮时,我们假设已经通过ajax获取到了后台返回的数据。那么我们需要赋初值了。
假设,我们编辑10班的数据,我修改一下数据后,是这样的:
但是,发现没有,当手动设置值的时候,男生人数竟然没有自动隐藏。
最后检查发现啊,是clsChange事件没有触发,但是我们知道ngmodelchange是angular内置的绑定事件,在patchvalue中是会被触发的。那只能说明在点击编辑按钮时,dom元素还没有渲染出来;由于我是在onInit里初始化的表单数据,当我把其放到构造函数中发现也不行。那么我们进一步测试,把弹出框的表单独立成一个组件,如下
父组件的代码:
最后测试的过程很复杂,就不记录了,把结果告诉大家:
结论:
1 表单回显时,初始化在oninit里面时,调用patchValue不会触发绑定在dom元素上的事件。因为在oninit周期内,dom元素还没有绑定方法。必须在ngafterviewinit里面去调用才可以。这是第一种思维方式。就是把表单联动的逻辑绑定在dom元素上时,调用的生命周期不是Oninit
2 如果不用上面的方法,我们可以利用valueChanges方法,直接监听表单数据,改变时控制其启用禁用状态从而来控制联动效果。我个人认为这样的写法比较好,层次比较分明,业务逻辑比较清晰。