0%

自定义表单

ControlValueAccessor

它是一个接口用于连接表单模型和视图,自定义表单必须实现这个接口,来实现模型与视图的映射关系

Angular引入它的原因在于不同的输入控件更新数据方式不同,input或checkbox,但是可以通过ControlValueAccessor统一

  • DefaultValueAccessor - text/textarea类型

  • SelectControlValueAccessor - selec类型

  • CheckboxControlValueAccessor - checkbox类型

实现ControlValueAccessor接口

1
2
3
4
5
6
export interface ControlValueAccessor {
writeValue(obj: any): void; // 将模型中的新值写入视图
registerOnChange(fn: any): void; // 当控件接收到change事件后,调用的函数,通知外部组件发生变化
registerOnTouched(fn: any): void; // 接收到touched事件后调用的函数
setDisabledState?(isDisabled: boolean): void; // 当控件状态变成DISABLED或ENABLE时,调用该函数启用或禁用dom
}
1
2
3
4
5
6
7
8
9
@Component(...)
class CounterComponent implements ControlValueAccessor {
...
propagateChange = (_: any) => {};
registerOnChange(fn: any) {
this.propagateChange = fn; // view层的值发生改变,通知外部
}
registerOnTouched...
}

注册成为表单控件

  • NG_VALUE_ACCESSOR: token类型为ControlValueAccessor,将控件本身注册到DI框架,使其可以被表单访问

  • NG_VALIDTORS: 将控件注册成为一个可以让表单得到其验证状态的控件,token为function或Validator,配合useExisting可以让控件只暴露出对应的function或Validator的validate方法

  • forwardRef: 向前引用,允许我们引用一个尚未定义的对象

  • multi: 设置为true,该token对应多个依赖项,使用相同的token获取依赖项的时候,获取的是已注册的依赖对象列表。如果不是true,那么对于相同的token的提供商来说,后定义的提供商会覆盖前面定义的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component({
selector: 'exe-demo',
...
provides: [
// 创建Token为NG_VALUE_ACCESSOR的提供商
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SomeComponent),
multi: true
},
// 创建Token为NG_VALIDATORS的表单验证验证器
{
provide: NG_VALIDATORS,
useValue: validateCounterRange,
multi: true
}
]
})

完整代码

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import { Component, forwardRef , Input } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS,
FormControl, ValidatorFn, ValidationErrors, AbstractControl } from '@angular/forms';
export const EXE_COUNTER_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CounterComponent),
multi: true
}
export const validateCounterRange: ValidatorFn = (control: AbstractControl) : ValidationErrors => {
return ( control.value > 10 || control.value < 0 ) ?
{
'rangeError': { current: control.value, max: 10, min: 0}
} : null
}
export const EXE_COUNTER_VALIDATOR = {
provide: NG_VALIDATORS,
useValue: validateCounterRange,
multi: true
}
@Component({
...
provides: [
EXE_COUNTER_VALUE_ACCESSOR,
EXE_COUNTER_VALIDATOR
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent implements ControlValueAccessor {
@Input() _count: number = 0;
get count() {
return this._count;
}
set count(value: number) {
this._count = value;
this.propagateChange(this._count);
}
propagateChange = (_: any) => {};
writeValue(value: any) {
if (value) {
this.count = value;
}
}
registerOnchange(fn: any) {
this.propagateChange = fn;
}
registerOnTouched(fn: any) {

}
increment() {
this.count++;
}
decrement() {
this.count--;
}
}

在Angular中通过Provider来描述与Token相关联的依赖对象的创建方式,分为以下四种

  • useClass

  • useValue

  • useExisting

  • useFactory

relation

useClass
1
2
3
providers: [ 
{ provide: ApiService, useClass: ApiService } // 简介写法直接 ApiService
]
useValue
1
2
3
providers: [ 
{ provide: API_URL, useValue: 'http://my.api.com/v1' }
]
useExisting
1
2
3
providers: [ 
{ provide: 'ApiServiceAlias', useValue: ApiService }
]
useFactory
1
2
3
4
5
6
7
export function configFactory(config: AppConfig) {
return () => config.load();
}
providers: [
{ provide: APP_INITIALIZER, useFactory: configFactory,
deps: [AppConfig, multi: true] }
]