目标:自定义表单控件(FormControl),增加灵活性和复用性。
场景:自定义组件作为表单控件,添加formControlName获取值。
报错:No value accessor for form control with name: ‘xxx’。
原因:将name绑定到了一个没有对应’value’的地方,或者说是找不到获取对应的’value’的通道。
解决方法1:添加ngDefaultControl元素
ngDefaultControl元素是基于DefaultValueAccessor指令的。
官方文档说明:
- DefaultValueAccessor是默认的ControlValueAccessor(值访问器),用于写入值,侦听输入元素的更改,该访问器由FormControlDirective、FormControlName和NgModel指令使用。
- 默认情况下,此值访问器用于
<input type=“text”>
和<textarea>
元素,但也可以将其用于具有类似行为且不需要特殊处理的自定义组件。为了将默认值访问器附加到自定义元素,请添加ngDefaultControl属性,如下所示。
总结: 报错是因为自定义组件没有对应的值访问器,需要自行额外添加。
例子:
<custom-input-component ngDefaultControl [(ngModel)]="value"></custom-input-component>
解决方法2:自定义组件实现ControlValueAccessor接口
说明: ControlValueAccessor接口是angular表单API和Dom元素的联系的通道。自定义组件实现该接口,为自定义元素提供了值访问器的同时,对值的获取和变化的处理也更加灵活。
方法: 该接口定义了四个方法,用来在表单模型与视图之间传递值和值的变化。
-
writeValue(obj: any): void
从表单API到Dom元素,传递值,即模型到视图的编程发生更改时,表单API会调用此方法写入视图。
一般用法:当表单控件内容发生改变时(表单初始化赋值或者控件patchValue、setValue),表单API自动调用该方法,方法参数为改变后的值,因此我们需要在方法内部写好把值赋给Dom元素的代码。
-
registerOnChange(fn: any): void
从Dom元素到表单API,传递变化,该方法注册一个回调函数,该控件的值在 UI 中更改时将调用该回调函数。
一般用法:用户界面输入发生变化,调用回调函数,该回调函数需要我们在新写的值访问器组件里自行定义,然后在registerOnChange进行保存以注册。每次UI发生变化时,调用注册的函数,将发生的变化传递给表单。
-
registerOnTouched(fn: any): void
,从组件到表单,注册了一个回调函数,获取视图中失去焦点(或者获取焦点)时,调用该方法。表单控件初始化时,表单formsAPI会调用该方法更新表单模型(失焦状态)。
-
setDisabledState(isDisabled: boolean)?: void
,从表单到组件,当控件状态切换为disabled(或者从disabled切换)时,调用该方法,传入参数为控件当前的disabled状态值。
例子:
@Component({
selector: 'rh-foundation-data-selector',
templateUrl: './foundation-data-infos.component.html',
styleUrls: ['./foundation-data-infos.component.less'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => RhFoundationDataInfosComponent),
multi: true
}
]
})
export class RhFoundationDataInfosComponent implements OnInit, ControlValueAccessor
- provide: NG_VALUE_ACCESSOR,用于为表单控件提供ControlValueAccessor的Token。
- useExisting: forwardRef(() => RhFoundationDataInfosComponent),forwardRef让我们在使用构造注入时,使用尚未定义的依赖对象类型。
- multi: true,可能存在多个表单自定义控件,都注册到了NG_VALUE_ACCESSOR的Token上面,因此设置为true。