October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run ScanOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
World desk4 min

NG_VALUE_ACCESSOR in Angular: Registering a Custom Form Control

NG_VALUE_ACCESSOR registers a ControlValueAccessor so Angular Forms can synchronize a custom component with form state. Learn the four methods, provider pattern, default accessor option, and key troubleshooting cases.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

NG_VALUE_ACCESSOR is the Angular dependency-injection token that registers a ControlValueAccessor so Angular Forms can connect a control to its view. The accessor carries values in two directions: writeValue sends a form-model value to the view, while the callback saved by registerOnChange sends a user edit back to the model. A custom control also needs to report touched state and, when supported, reflect disabled state.

What NG_VALUE_ACCESSOR does

Angular defines ControlValueAccessor as “a bridge between the Angular forms API and a native element in the DOM.” NG_VALUE_ACCESSOR is the token through which an accessor is provided for Angular’s form directives to find. This is what lets a component participate in reactive forms with formControl or formControlName, and template-driven forms with ngModel.

As an Amazon Associate I earn from qualifying purchases.

The accessor separates two directions of synchronization. A form-model update flows into the component through writeValue. A user interaction flows back through the function Angular supplies to registerOnChange. Angular’s forms guide describes this propagation in both reactive and template-driven forms.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How the four ControlValueAccessor methods work

Method Direction or purpose What the component should do
writeValue(value) Model to view Update the displayed or internal value from Angular. Do not treat this as a user edit or call the registered change callback from it.
registerOnChange(fn) View to model Save Angular’s callback and invoke it when user interaction changes the control’s value.
registerOnTouched(fn) Touched-state notification Save the callback and call it when the control is blurred or otherwise should count as touched.
setDisabledState(isDisabled) Disabled-state update When implemented, apply the status to the actual interactive UI, not merely to a visual label.

The distinction between writeValue and the change callback prevents a feedback loop: Angular can update the view without the component reporting that update as a fresh user input. Touched and disabled states are separate from the value itself.

Register a custom accessor with NG_VALUE_ACCESSOR

For a conventional custom component, provide the component itself under NG_VALUE_ACCESSOR. Use useExisting so Angular reuses the component instance, and multi: true so the provider joins the available accessor providers.

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

@Component({
  selector: 'app-rating',
  template: `
    <button type="button"
            [disabled]="disabled"
            (click)="choose(1)"
            (blur)="markTouched()">1</button>
    <button type="button"
            [disabled]="disabled"
            (click)="choose(2)"
            (blur)="markTouched()">2</button>
  `,
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => RatingComponent),
    multi: true
  }]
})
export class RatingComponent implements ControlValueAccessor {
  value: number | null = null;
  disabled = false;

  private onChange: (value: number | null) => void = () => {};
  private onTouched: () => void = () => {};

  writeValue(value: number | null): void {
    this.value = value;
  }

  registerOnChange(fn: (value: number | null) => void): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: () => void): void {
    this.onTouched = fn;
  }

  setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

  choose(value: number): void {
    if (this.disabled) return;
    this.value = value;
    this.onChange(value);
  }

  markTouched(): void {
    this.onTouched();
  }
}

This example shows the contract rather than a complete rating widget: a real control would render its selected value and expose its actual interaction semantics. The key points are that model writes only update component state, user choices call the saved change callback, and blur reports touched state.

Choose the default accessor or a custom one

Use Angular’s default accessor for text-like behavior

Angular’s DefaultValueAccessor handles ordinary text inputs and textareas. The ngDefaultControl directive can also be used on a custom element whose value behavior is similar and does not need special processing. This is appropriate only when the element’s interaction and value semantics match the default behavior.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Implement a custom accessor for custom value semantics

If a widget has a distinct value representation or interaction—such as a rating picker, range control, or composite input—implement ControlValueAccessor and register it. For select controls, Angular’s SelectControlValueAccessor supports compareWith to customize how option identity is compared, which is useful when options are objects rather than primitive values.

Handle “No value accessor” errors

If Angular reports that no value accessor is available for a form control, check the connection from the form directive to the control rather than changing the value logic at random:

  • Confirm the element is inside the expected form context and uses the intended directive, such as formControl, formControlName, or ngModel.
  • For a custom component, confirm it implements ControlValueAccessor and that its provider uses NG_VALUE_ACCESSOR, useExisting, and multi: true.
  • Confirm the relevant Angular Forms package is imported in the application or feature context where the control is used.
  • If the element is a basic text-like custom element without special semantics, consider ngDefaultControl; do not use it to mask a genuinely custom interaction model.
  • If the component injects its own NgControl as part of an Angular Material custom form field, check the version-specific case below before adding a provider.

Angular Material custom form fields and the NgControl cycle

The Angular Material v9 guide documents a narrow exception: a custom form-field component that injects its own NgControl can create a cyclic dependency if it also provides NG_VALUE_ACCESSOR. That guide’s workaround is to assign the accessor directly when the control exists:

if (ngControl != null) {
  ngControl.valueAccessor = this;
}

This is not a general replacement for the provider pattern. It addresses the particular injection arrangement documented in the Angular Material v9 custom form-field guide. Because that guide is for v9, verify the guidance against the Angular Material version and component architecture in your project.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Signal Forms are a separate custom-control route

For Angular Signal Forms, the migration guide describes custom-control interfaces such as FormValueControl and FormCheckboxControl. It explicitly says not to implement those interfaces together with ControlValueAccessor on the same component. Treat this as a separate forms architecture rather than adding both integration mechanisms to one control; see the Signal Forms migration guide.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.