ControlValueAccessor is the bridge that makes a custom Angular component behave like a real form control. The useful comparison is not just value syncing, but validation, touched or dirty state, disabled propagation, and the production pitfalls of relying on custom two-way binding instead.
What Is ControlValueAccessor in Angular and Why Is It Better Than Custom Two-Way Binding?
Forms-integration questionControlValueAccessor is not just another way to sync a value. It is the contract that lets Angular Forms treat your custom component like a real form control. That is the production difference: validation, touched/dirty state, reset behavior, and disabled handling work correctly instead of being rebuilt with fragile custom two-way bindings.
Why not just use custom two-way binding?
Two-way binding like [(value)] only synchronizes a value. It does not integrate with Angular Forms, so you lose validation, touched/dirty states, disabled state, and form-level APIs. CVA plugs your component into the entire forms ecosystem.
Capability | Custom [(value)] binding | ControlValueAccessor |
|---|---|---|
Works with Reactive Forms / ngModel | ❌ No | ✅ Yes |
Supports validators | ❌ No | ✅ Yes |
Tracks touched / dirty / pristine | ❌ No | ✅ Yes |
Supports disabled state | ❌ Manual hacks | ✅ Built-in via setDisabledState |
Integrates with FormGroup / FormControl | ❌ No | ✅ Yes |
Minimal CVA implementation
A CVA has four responsibilities:
• writeValue: Angular → component
• registerOnChange: component → Angular
• registerOnTouched: mark as touched
• setDisabledState: handle disabled
import { Component, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
selector: 'app-rating',
template: `
<button *ngFor="let n of [1,2,3,4,5]" (click)="set(n)" [disabled]="disabled">
{{ n }}
</button>
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => RatingComponent),
multi: true
}
]
})
export class RatingComponent implements ControlValueAccessor {
value = 0;
disabled = false;
private onChange = (v: number) => {};
private onTouched = () => {};
writeValue(v: number): void {
this.value = v ?? 0;
}
registerOnChange(fn: (v: number) => void): void {
this.onChange = fn;
}
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
set(v: number) {
if (this.disabled) return;
this.value = v;
this.onChange(v); // notify Angular Forms
this.onTouched(); // mark as touched
}
}
Using it like a real form control
<form [formGroup]="form">
<app-rating formControlName="rating"></app-rating>
</form>
Why this is strictly better than custom two-way binding
With CVA, your component becomes a first-class Angular form control. It participates in validation, form submission, reset, disabled state propagation, and form-level state tracking. Two-way binding only moves a value around—it does not integrate into the forms system.
Real-world requirement | Without CVA | With CVA |
|---|---|---|
Disable the whole form | ❌ You must wire every input manually | ✅ setDisabledState is called automatically |
Mark control as touched | ❌ Manual hacks | ✅ Angular handles it |
Show validation errors | ❌ Not integrated | ✅ Works via FormControl |
Reset the form | ❌ Manual reset logic | ✅ Angular resets automatically |
Senior-level pitfalls
• Forgetting to call onTouched() (control never becomes touched)
• Not implementing setDisabledState (disabled forms don’t work)
• Mutating internal state without calling onChange
• Using custom two-way binding for components that should be form controls
Interview summaryControlValueAccessor is Angular’s official way to make a custom component behave like a real form control. It integrates with Reactive Forms and ngModel, supports validation, touched/dirty state, disabled handling, and form APIs. Custom two-way binding only syncs a value and does not integrate with Angular Forms, which makes it unsuitable for serious form components.
Use the relevant interview-question hub first, then move into a concrete study plan before targeted company sets.