Don’t reinvent the wheel when implementing ControlValueAccessor

Siyang Kern Zhao
Angular In Depth
Published in
4 min readNov 11, 2019

Reuse already-implemented controlValueAccessor

Photo by Clint Adair on Unsplash

AngularInDepth is moving away from Medium. More recent articles are hosted on the new platform inDepth.dev. Thanks for being part of indepth movement!

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>

I would really recommend you pause reading and give yourself several minutes to think about how would you solve this problem.

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.

--

--