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.
Use this Angular interview question to rehearse a quick answer, common mistake, follow-up, and production pitfall.
What Is ControlValueAccessor in Angular and Why Is It Better Than Custom Two-Way Binding?Frontend interview answer
This Angular interview question tests whether you can explain Angular ControlValueAccessor vs custom two-way binding: real forms integration and pitfalls, connect it to production trade-offs, and handle common follow-up questions.
- Angular ControlValueAccessor vs custom two-way binding: real forms integration and pitfalls explanation without falling back to memorized docs wording
- Forms and Controlvalueaccessor reasoning, edge cases, and production failure modes
- How you would answer the most likely Angular interview follow-up
Forms contractControlValueAccessor is not just another way to sync a value. It is the contract that makes a custom Angular component behave like a real form control inside Angular Forms. That is the production difference: validation, touched/dirty state, reset behavior, and disabled handling keep working instead of being rebuilt with fragile custom two-way bindings.
Broken vs correct example
A plain @Input() value plus @Output() valueChange can mirror text, but it does not automatically propagate markAsTouched(), setDisabledState(), or form resets. A proper CVA implements writeValue, registerOnChange, registerOnTouched, and setDisabledState so the Angular form API stays in charge.
Decision rule
- Use simple input/output binding for isolated value sync outside Angular Forms.
- Use CVA the moment the component must act like a real form control.
- If disabled, touched, validation, or reset behavior matters, custom two-way binding stops being enough.
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 this as one explanation rep, then continue with the Angular interview questions cluster or a guided prep path.