ControlValueAccessor interface.The ControlValueAccessor Interface
ControlValueAccessor is an interface for communication between a FormControl and the native element. It abstracts the operations of writing a value and listening for changes in the DOM element representing an input control. The following snippet was taken from the Angular source code, along with the original comments:
The ControlValueAccessor interface.
interface ControlValueAccessor {
/**
* Write a new value to the element.
*/
writeValue(obj: any): void;
/**
* Set the function to be called when the control receives a change event.
*/
registerOnChange(fn: any): void;
/**
* Set the function to be called when the control receives a touch event.
*/
registerOnTouched(fn: any): void;
/**
* This function is called when the control status changes to or from "DISABLED".
* Depending on the value, it will enable or disable the appropriate DOM element.
* @param isDisabled
*/
setDisabledState?(isDisabled: boolean): void;
}
ControlValueAccessor Directives
Each time you use the formControl or formControlName directive on a native <input> element, one of the following directives is instantiated, depending on the type of the input:
- DefaultValueAccessor – Deals with all input types, excluding checkboxes, radio buttons, and select elements.
- CheckboxControlValueAccessor – Deals with checkbox input elements.
- RadioControlValueAccessor – Deals with radio control elements [RH: Or just “radio buttons”
or “radio button inputs”?]. - SelectControlValueAccessor – Deals with a single select element.
- SelectMultipleControlValueAccessor – Deals with multiple select elements.
Become part of our International JavaScript Community now!
LEARN MORE ABOUT iJS:
Let’s peek under the hood of the CheckboxControlValueAccessor directive to see how it implements the ControlValueAccessor interface. The following snippet was taken from the Angular docs:
checkbox_value_accessor.ts.
import {Directive, ElementRef, Renderer, forwardRef} from '@angular/core'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor'; export const CHECKBOX_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CheckboxControlValueAccessor), multi: true, }; @Directive({ selector : `input[type=checkbox][formControlName], input[type=checkbox][formControl], input[type=checkbox][ngModel]`, host : { '(change)': 'onChange($event.target.checked)', '(blur)' : 'onTouched()' } , providers: [CHECKBOX_VALUE_ACCESSOR] }) export class CheckboxControlValueAccessor implements ControlValueAccessor { onChange = (_: any) => {}; onTouched = () => {}; constructor(private _renderer: Renderer, private _elementRef: ElementRef) {} writeValue(value: any): void { this._renderer.setElementProperty(this._elementRef.nativeElement, 'checked', value); } registerOnChange(fn: (_: any) => {}): void { this.onChange = fn; } registerOnTouched(fn: () => {}): void { this.onTouched = fn; } setDisabledState(isDisabled: boolean): void { this._renderer.setElementProperty(this._elementRef.nativeElement, 'disabled', isDisabled); } }
Let’s explain what’s going on:
- This directive is instantiated when an input of type
checkboxis declared with theformControl,formControlName, orngModeldirectives. - The directive listens to
changeandblurevents in thehost. - This directive will change both the
checkedanddisabledproperties of the element, so the
ElementRefandRenderer[RH: ElementRef and Renderer what? Classes?] are injected. - The
writeValue()implementation is straight forward: it sets thecheckedproperty of the
native element. Similarly,setDisabledState()sets thedisabledproperty. - The function being passed to the
registerOnChange()method is responsible for updating the outside world about changes to the value. It is called in response to achangeevent with the input value. - The function being passed to the
registerOnTouched()method is triggered by theblurevent. - Finally, the
CheckboxControlValueAccessordirective is registered as a provider.
Sample Custom Form Control: Button Group
Let’s build a custom FormControl based on the Twitter Bootstrap button group component.
We will start with a simple component:
custom-control.component.ts.
import {Component} from "@angular/core";
@Component({
selector : 'rf-custom-control',
templateUrl: 'custom-control.component.html',
})
export class CustomControlComponent {
private level: string;
private disabled: boolean;
constructor(){
this.disabled = false;
}
public isActive(value: string): boolean {
return value === this.level;
}
public setLevel(value: string): void {
this.level = value;
}
}
Here is the template:
custom-control.component.html.
<div class="btn-group btn-group-lg">
<button type="button"
class="btn btn-secondary"
[class.active]="isActive('low')"
[disabled]="disabled"
(click)="setLevel('low')">low</button>
<button type="button"
class="btn btn-secondary"
[class.active]="isActive('medium')"
[disabled]="disabled"
(click)="setLevel('medium')">medium</button>
<button type="button"
class="btn btn-secondary"
[class.active]="isActive('high')"
[disabled]="disabled" (click)="setLevel('high')">high</button>
</div>
Next, let’s implement the ControlValueAccessor interface:
custom-control.ts component class.
export class CustomControlComponent implements ControlValueAccessor {
private level: string;
private disabled: boolean;
private onChange: Function;
private onTouched: Function;
constructor() {
this.onChange = (_: any) => {};
this.onTouched = () => {};
this.disabled = false;
}
public isActive(value: string): boolean {
return value === this.level;
}
public setLevel(value: string): void {
this.level = value;
this.onChange(this.level);
this.onTouched();
}
writeValue(obj: any): void {
this.level = obj;
}
registerOnChange(fn: any): void{
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
}
The last step is to register our custom control component under the NG_VALUE_ACCESSOR token. NG_VALUE_ACCESSOR is an OpaqueToken used to register multiple ControlValue providers. (If you are not familiar with OpaqueToken, the multi property, and the forwardRef() function, read the official dependency injection guide on the Angular website.)
Here’s how we register the CustomControlComponent as a provider:
Registering the control as a provider.
const CUSTOM_VALUE_ACCESSOR: any = {
provide : NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomControlComponent),
multi : true,
};
@Component({
selector : 'app-custom-control',
providers : [CUSTOM_VALUE_ACCESSOR],
templateUrl: 'custom-control.component.html',
})
Our custom control is ready. Let’s try it out:
app.component.ts.
import {Component, OnInit} from "@angular/core";
import {FormControl} from "@angular/forms";
@Component({
selector: 'rf-root',
template: `
<div class="container">
<h1 class="h1">REACTIVE FORMS</h1>
<rf-custom-control [formControl]="buttonGroup"></rf-custom-control>
<pre>
<code>
Control dirty: {{buttonGroup.dirty}}
Control touched: {{buttonGroup.touched}}
</code>
</pre>
</div>
`,
})
export class AppComponent implements OnInit {
public buttonGroup: FormControl;
constructor() {
this.buttonGroup = new FormControl('medium');
}
ngOnInit(): void {
this.buttonGroup.valueChanges.subscribe(value => console.log(value));
}
}
This tutorial is an excerpt from iJS speaker Nir Kaufman’s eBook “Angular Reactive Forms – A comprehensive guide for building forms with Angular”. The complete book can be purchased in the Leanpub store: https://leanpub.com/angular-forms






6 months access to session recordings.