Angular20 min read

Custom Form Controls with ControlValueAccessor

Integrate custom UI widgets (date pickers, rating stars) with Angular forms like built-in inputs.

Grace Thompson
August 6, 2025
9.1k244

ControlValueAccessor (CVA) lets you build a custom input component that works with Angular forms.

Example custom controls:
- star rating
- phone input with country code
- date picker wrapper

## Goal: Star rating input that works with formControlName

### Step 1: Create a rating component

```ts
import { Component, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-rating',
  standalone: true,
  imports: [CommonModule],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => RatingComponent),
      multi: true,
    },
  ],
  template: `
    <div class="stars">
      <button type="button" *ngFor="let s of stars" (click)="set(s)">
        {{ s <= value ? '★' : '☆' }}
      </button>
    </div>
  `,
})
export class RatingComponent implements ControlValueAccessor {
  stars = [1, 2, 3, 4, 5];
  value = 0;
  disabled = false;

  private onChange: (v: number) => void = () => {};
  private onTouched: () => void = () => {};

  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);
    this.onTouched();
  }
}
```

### Step 2: Use it in a reactive form

```html
<form [formGroup]="form">
  <app-rating formControlName="rating"></app-rating>
</form>
```

Now your custom component behaves like a native input.

> Next: State management options (Signals store patterns, NgRx, Akita style decisions).
#Angular#Forms#Advanced