Don’t reinvent the wheel when implementing ControlValueAccessor

Siyang Kern Zhao
Nov 11, 2019 · 3 min read

Reuse already-implemented controlValueAccessor

Photo by Clint Adair on Unsplash

Prerequisite

You might have already know the technique of implementing ControlValueAccessor (CVA) to adapt your custom form element to Angular’s Reactive Form, such as a 1–5 star rating component.

CVA is a prerequisite knowledge before you read this article. If you don’t know what’s CVA please make sure you go over this simple overview or this more comprehensive article.

Problem

Let’s say you need to make a component A that wraps a native form element and you want to be able to apply formControl and formControlName directly on component A as if it were applied directly on the native form element.

For example, you need to create a resettable-input component (because this resetting behaviour is everywhere in your app), which contains an input element and a button, and the button click simply clears the input:

Also, you want your resettable-input component to be compatible with Reactive Form so you can apply formControl or formControlName directly on resettable-input:

<form [formGroup]="formGroup">

First Name
<resettable-input [formControlName]="'firstName'"></resettable-input>

Last Name
<resettable-input [formControlName]="'lastName'"></resettable-input>

</form>

Naturally you would go ahead and implement ControlValueAccessor on resettable-input component like this:

@Component({
selector: 'resettable-input',
template: `
<input type="text" (blur)="onBlur()" (input)="onInputChange()" #input>
<button (click)="clearInput()">clear</button>
`,
providers: [{
provide: NG_VALUE_ACCESSOR, useExisting: ResettableInputComponent, multi: true
}]
})
export class ResettableInputComponent implements ControlValueAccessor {

onTouched = () => {};
onChange = _ => {};

@ViewChild('input', {static: true, read: ElementRef})
inputElementRef: ElementRef;

constructor(private _renderer: Renderer2) {}

clearInput() {
this._renderer.setProperty(this.inputElementRef.nativeElement, 'value', '');
this.onChange('');
}

onInputChange() {
const value = this.inputElementRef.nativeElement.value;
this.onChange(value);
}

onBlur() {
this.onTouched();
}

registerOnTouched(fn: any) {
this.onTouched = fn;
}

registerOnChange(fn: any) {
this.onChange = fn;
}

writeValue(value: string) {
this._renderer.setProperty(this.inputElementRef.nativeElement, 'value', value);
}

setDisabledState(isDisabled: boolean): void {
this._renderer.setProperty(this.inputElementRef.nativeElement, 'disabled', isDisabled);
}

}

This works very well. However, you may or may not be aware that: you are re-inventing the wheel. In fact, Angular has already implemented ControlValueAccessor for input element as well as a bunch of other native form element like checkbox, radio etc. They definitely have well-tested comprehensive functionality compared to your implementation. The question now becomes: How to reuse? Let’s see solution below:

@Component({
selector: 'resettable-input',
template: `
<input type="text" [formControl]="control">
<button (click)="clearInput()">clear</button>
`,
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: ResettableInputComponent,
multi: true
}]
})
export class ResettableInputComponent implements ControlValueAccessor {

@ViewChild(FormControlDirective, {static: true})
formControlDirective: FormControlDirective;
@Input()
formControl: FormControl;

@Input()
formControlName: string;
/* get hold of FormControl instance no matter formControl or formControlName is given. If formControlName is given, then this.controlContainer.control is the parent FormGroup (or FormArray) instance. */
get control() {
return this.formControl || this.controlContainer.control.get(this.formControlName);
}

constructor(private controlContainer: ControlContainer) {
}

clearInput() {
this.control.setValue('');
}

registerOnTouched(fn: any): void {
this.formControlDirective.valueAccessor.registerOnTouched(fn);
}

registerOnChange(fn: any): void {
this.formControlDirective.valueAccessor.registerOnChange(fn);
}

writeValue(obj: any): void {
this.formControlDirective.valueAccessor.writeValue(obj);
}

setDisabledState(isDisabled: boolean): void {
this.formControlDirective.valueAccessor.setDisabledState(isDisabled);
}
}

This is working well and we successfully avoid re-implementing ControlValueAccessor for the native input element.

Here we use@ViewChild to get hold of formControlDirective that is automatically bound to text input with formControl directive, and it’s field valueAccessor is already there in Angular and we just need to connect with our implementation of ControlValueAccessor.

Conclusion

If we want to make a custom form component that’s compatible with Reactive Form, we need to implement ControlValueAccessor. However, if the custom form component is compositing an existing native form element or another existing custom form component that has already implemented ControlValueAccessor, it is possible and preferred to reuse existing ControlValueAccessor without implementing ControlValueAccessor from scratch again.

Final Code included here:

Big thanks to Tim Deschryver, Nacho Vazquez Calleja and Andrew Evans who have given great feedbacks.

Angular In Depth

The place where advanced Angular concepts are explained

Thanks to Andrew Evans and Nacho Vazquez Calleja

Siyang Kern Zhao

Written by

Independent Contractor, Angular/NodeJS Developer

Angular In Depth

The place where advanced Angular concepts are explained

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade