Understanding Angular's Control Value Accessor Interface
Jennifer Wadella
Posted on July 2, 2019
If you're dealing with forms in Angular on a regular basis, one of the most powerful things you can learn is how to use the Control Value Accessor interface. The CVA interface is a bridge between FormControls and their elements in the DOM. A component extending the CVA interface can create a custom form control that behaves the same as a regular input or radio button.
Why Would You Want to Use the Control Value Accessor Interface?
Sometimes you may need to create a custom form element that you want to be able to use as a regular FormControl. (For a better understanding of FormControls and other Angular Form classes you might want to read my article here) For example, creating a 5 star rating UI that updates a single value. We'll use this example in our demo.
There's a lot happening in the UI here - stars changing colors as they're hovered over and displaying different text for each ratings, but all we care about is saving a number value 0-5.
Implementing the CVA
To use the CVA interface in a component, you must implement its three required methods: writeValue
, registerOnChange
, and registerOnTouched
. There is also an optional method setDisabledState
.
The writeValue
method is called in 2 situations:
- When the formControl is instantiated
rating = new FormControl({value: null, disabled: false})
- When the formControl value changes
rating.patchValue(3)
The registerOnChange
method should be called whenever the value changes - in our case, when a star is clicked on.
The registerOnTouched
method should be called whenever our UI is interacted with - like a blur event. You may be familiar with implementing Typeaheads from a library like Bootstrap or NGX-Bootstrap that has an onBlur
method.
The setDisabledState
method is called in 2 situations:
- When the formControl is instantiated with a disabled prop
rating = new FormControl({value: null, disabled: false})
- When the formControl disabled status changes
rating.disable();
rating.enable();
A star rating component implementing the CVA may look something like this:
export class StarRaterComponent implements ControlValueAccessor {
public ratings = [
{
stars: 1,
text: 'must GTFO ASAP'
},
{
stars: 2,
text: 'meh'
},
{
stars: 3,
text: 'it\'s ok'
},
{
stars: 4,
text: 'I\'d be sad if a black hole ate it'
},
{
stars: 5,
text: '10/10 would write review on Amazon'
}
]
public disabled: boolean;
public ratingText: string;
public _value: number;
onChanged: any = () => {}
onTouched: any = () => {}
writeValue(val) {
this._value = val;
}
registerOnChange(fn: any){
this.onChanged = fn
}
registerOnTouched(fn: any){
this.onTouched = fn
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
setRating(star: any) {
if(!this.disabled) {
this._value = star.stars;
this.ratingText = star.text
this.onChanged(star.stars);
this.onTouched();
}
}
}
You must also tell Angular that your component implementing the CVA is a value accessor(remember, interfaces aren't compiled in TypeScript) using NG_VALUE_ACCESSOR and forwardRef.
import { Component, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
selector: 'gr-star-rater',
templateUrl: './star-rater.component.html',
styleUrls: ['./star-rater.component.less'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => StarRaterComponent),
multi: true
}
]
})
export class StarRaterComponent implements ControlValueAccessor {
...
Using Your New CVA Component
Now, to use your fancy new CVA component, you can treat is as a plain old FormControl.
this.galaxyForm = new FormGroup({
rating: new FormControl({value: null, disabled: true})
});
<form [formGroup]="galaxyForm" (ngSubmit)="onSubmit()">
<h1>Galaxy Rating App</h1>
<div class="form-group">
<label>
Rating:
<gr-star-rater formControlName="rating"></gr-star-rater>
</label>
</div>
<div class="form-group">
<button type="submit">Submit</button>
</div>
</form>
Tada! Not so scary, huh? Questions or need help with Angular Reactive Forms? Let me know!
Posted on July 2, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.