Reusable Form Controls in Angular
using ControlValueAccessor
Syed M. Taha (@smtaha512)
Software Engineer

- Defines an interface that acts as a bridge between the Angular forms API and a native element in the DOM.
ControlValueAccessor
- Implement this interface to create a custom form control directive that integrates with Angular forms.
@smtaha512
Learning by Example
@smtaha512
Rating Component
@smtaha512
<div>
<img
class="star"
[ngClass]="{
disabled: isDisabled,
selected:
selectedRating?.star !== null &&
selectedRating?.star >= i
}"
*ngFor="let item of ratings; let i=index;"
src="/assets/star.svg"
(click)="onStarClick(item)"
/>
</div>
<p>{{ selectedRating?.text }}</p>
Rating component
@smtaha512
@Component({
selector: 'app-rating',
templateUrl: './rating.component.html',
styleUrls: ['./rating.component.scss']
})
export class RatingComponent implements OnInit {
ratings = [
{ star: 0, text: 'Worst' },
{ star: 1, text: 'Bad' },
{ star: 2, text: 'Satisfactory' },
{ star: 3, text: 'Good' },
{ star: 4, text: 'Best' }
];
@Output() ratingChange = new EventEmitter();
selectedRating = this.ratings[0];
isDisabled: boolean;
constructor() {}
ngOnInit() {}
onStarClick(selectedRating: { star: number; text: string }) {
this.selectedRating = selectedRating;
this.ratingChange.emit(selectedRating.star);
}
}
Rating component
@smtaha512
<app-rating (ratingChange)="onRatingChange($event)">
</app-rating>
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
private readonly form = new FormGroup({
rating: new FormControl()
});
onRatingChange(event) {
this.rating.setValue(event);
}
get rating() {
return this.form.get('rating');
}
}
Parent component
@smtaha512
All set. We don't need ControlValueAccessor
@smtaha512
No, we do. Why?
- Validations???
- Managing extra code everywhere we use rating component for:
- Setting values
- Change listening
@smtaha512
Coming towards ControlValueAccessor
@smtaha512
To make any component / directive ControlValueAccessor, we have to:
- Register itself as NG_VALUE_ACCESSOR
- Implement ControlValueAccessor interface
@smtaha512
Registering as NG_VALUE_ACCESSOR
@Component({
selector: 'app-rating',
templateUrl: './rating.component.html',
styleUrls: ['./rating.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => RatingComponent),
multi: true
}
]
})
export class RatingComponent { }
@smtaha512
Implement ControlValueAccessor interface
@smtaha512
interface ControlValueAccessor {
writeValue(obj: any): void;
registerOnChange(fn: any): void;
registerOnTouched(fn: any): void;
setDisabledState?(isDisabled: boolean): void;
}
@smtaha512
This method is called by the forms API to write to the view when programmatic changes from model to view are requested.
Native / Custom Input
CVA
Angular Form
writeValue(value)
Communication
interface
specific to
form control
writeValue(value: any): void
@smtaha512
registerOnChange(fn: (_: any) => void): void
This method is called by the forms API on initialization to update the form model when values propagate from the view to the model.
Native / Custom Input
CVA
Angular Form
registerOnChange(fn)
Communication
interface
specific to
form control
fn: (_: any) => void
@smtaha512
registerOnTouched(fn: any): void
This method is called by the forms API on initialization to update the form model on blur.
Native / Custom Input
CVA
Angular Form
registerOnTouched(fn)
Communication
interface
specific to
form control
fn: (_: any) => void
@smtaha512
setDisabledState?(isDisabled: boolean): void
Function that is called by the forms API when the control status changes to or from 'DISABLED'. Depending on the status, it enables or disables the appropriate DOM element.
Native / Custom Input
CVA
Angular Form
setDisabledState(isDisabled)
Communication
interface
specific to
form control
@smtaha512
Implement ControlValueAccessor interface
@Component({})
export class RatingComponent implement ControlValueAccessor {
value: any;
disable: boolean;
onChange: any = () => { };
onTouched: any = () => { };
writeValue(obj: any): void {
this.value = obj;
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = onTouched;
}
setDisabledState?(isDisabled: boolean): void {
this.disabled = isDisabled;
}
}
@smtaha512
CVA Demo: https://github.com/smtaha512/cva-demo

Resources:
- Never again be confused when implementing ControlValueAccessor in Angular form by Max Koretskyi aka Wizard. (https://blog.angularindepth.com/never-again-be-confused-when-implementing-controlvalueaccessor-in-angular-forms-93b9eee9ee83)
-
The Control Value Accessor by Jennifer Wadella. (https://www.youtube.com/watch?v=kVbLSN0AW-Y&t=843s)
-
Angular Source Code. (github.com/angular/angular)
-
Angular documentation. (https://angular.io/api/forms/ControlValueAccessor)
Thank you
@smtaha512
s.m.taha10@gmail.com
Custom Form Controls in Angular
By Syed M. Taha
Custom Form Controls in Angular
- 17