[Angular] Implementing a ControlValueAccessor
So when you need to create a ControlValueAccessor?
When you want to use a custom component as form control. For example a counter component, you can custom the style and the way to control the value changes.
It needs some setup for control value accessor, but after you have done it once, you can prettty much just copy & paste the same template around to create a control value access.
import { Component, Input, forwardRef } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; const COUNTER_VALUE_ACCESSOR = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => StockCounterComponent), multi: true }; @Component({ selector: 'stock-counter', providers: [COUNTER_VALUE_ACCESSOR], styleUrls: ['stock-counter.component.scss'], template: ` ... ` }) export class StockCounterComponent implements ControlValueAccessor { value: number;
disabled = false; writeValue(val: number) { this.value = val; } registerOnChange(fn: any) { this.onModelChange = fn; } registerOnTouched(fn: any) { this.onTouch = fn; }
setDisabledState(isDisabled: boolean) {
this.disabled = isDisabled;
} ... }
So the template is somehow like the code above, highly recommend to create any snippet or live template in your IDE.
So inside the component we create, we have three methods,
- writeValue(obj: any)
- registerOnChange(fn: Function)
- registerOnTouched(fn: Function)
- setDisabledState(isDisabled: boolean): void
To understand each method is the key point to understand how control value access.
So first 'writeValue':
value: number; writeValue(val: number) { this.value = val; }
This is for setting initial value, we take value from our form builder and pass to our component via 'writeValue'.
<stock-counter [step]="10" [min]="10" [max]="1000" formControlName="quantity">
So the value comes from 'formControlName="quantity"'.
Now at the point, you can think that our form holds a model value and our component also holds a view value.
We need to sync model value and view value.
The way to do that is by "registerOnChange":
registerOnChange(fn: any) { this.onModelChange = fn; }
increment() { this.value = this.value === this.max ? this.value : this.value + this.step; this.onModelChange(this.value); }
Every time our view value (component value) changed, to need to notify our form about the changes, we need to call 'this.onModelChange(this.value)' function and pass in the changes value.
After this form will be able to the updated value.
OK, now we able to sync the value from our component to our form. But you might think about this can be easily done by EventEmitter, when border to create Control value accessor? The most important reason is that "Validation"!
html "form" component actually does lots of thing underhook. What example, it set field state "untouch", "touched", "dirty", "prinstin". We use those status to do validation and error messages.
For example:
input.ng-touched.ng-invalid { border-left: 5px solid red; }
If the field is touched and is invalid, we set the border to red.
Currently, we have our value synced, and control value access also helps us to add form validation ability to our component. But once the value changed, our component still show 'ng-untouch':
So we need "registerOnTouched" function to help us to do that:
registerOnTouched(fn: any) { this.onTouch = fn; }
increment() { this.value = this.value === this.max ? this.value : this.value + this.step; this.onModelChange(this.value); this.onTouch(); }
Now after the value changed, our component will be set 'ng-touched', now we are fully conver our component to a form component.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具