What Is ControlValueAccessor in Angular and Why Is It Better Than Custom Two-Way Binding?

LowEasyAngular
Preparing for interviews?

Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.

Quick Answer

ControlValueAccessor (CVA) is Angular’s official bridge between custom components and Angular Forms. It lets your component behave like a real form control (ngModel, formControl, formControlName) with validation, touched/dirty state, and disabled handling—unlike custom two-way binding which only syncs values and breaks form integration.

Answer

Core idea

ControlValueAccessor (CVA) is the interface that tells Angular Forms how to write a value into your component and how your component reports changes back. If you want a custom input component to work with ngModel, formControl, or formControlName, you must implement CVA.

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

Two-way binding vs ControlValueAccessor

Minimal CVA implementation

A CVA has four responsibilities:
writeValue: Angular → component
registerOnChange: component → Angular
registerOnTouched: mark as touched
setDisabledState: handle disabled

TYPESCRIPT
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

HTML
<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

Why CVA scales in real apps

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 summary

ControlValueAccessor 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.

Similar questions
Guides
29 / 37