Angular's Control Value Accessor

June 2020

Why would you want to implement the Control Value Accessor interface?

The fact that you are here probably means that you have googled for Control Value Accessor because you had a specific need. I can imagine this need to be one of the following:

How to implement the Control Value Accessor interface

The implementation seems a bit daunting, because you'll have to do a lot of stuff that you actually don't "want" to do, and this will add quite a bit of boilerplate to your custom component. Unfortunately this is just the way it is with the CVA (Control Value Accessor). So let's get started with the parts that you can just copy paste, don't really need to understand and that will always stay the same for each and every CVA you implement.

Boilerplate (= the stuff you can just copy paste)

First, you'll need to add some weird stuff to your component annotation:

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


@Component({
  selector: 'app-custom-input',
  templateUrl: './custom-input.component.html',
  styleUrls: ['./custom-input.component.scss'],

  // Step 1: copy paste this providers property
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomInputComponent),
      multi: true
    }
  ]
})
// Step 2: Add "implements ControlValueAccessor"
export class CustomInputComponent implements ControlValueAccessor {

Also append implements ControlValueAccessor in order to make sure you implement the Control Value Accessor interface correctly. Next, you'll need to add two methods to your class:

// Step 3: Copy paste this stuff here into your class
onChange: any = () => {}
onTouch: any = () => {}
registerOnChange(fn: any): void {
  this.onChange = fn;
}
registerOnTouched(fn: any): void {
  this.onTouch = fn;
}

You don't really need to understand this in depth. It just gives Angular the means to do it's thing when setting up everything necessary for the custom form control. What's important to understand, is that you don't provide implementations for the onTouch and onChange functions yourself. They will be provided by Angular. What onChange is doing, is to write any changes inside of your component to the form control. And onTouch will mark a form control as dirty. What you do get a say in is when you want to call those functions. So if you never call onChange, the form control on the ouside will never be updated. We'll understand better how that works in the next section.

The stuff that needs to be customized

Let's assume that we're implementing a component that revolves around a <input type="text"/>. For a button or any other thing, that could supply us with a value to put into the formControl on the parent component, the process will be similar.

First, we'll need to add the last missing function of the Control Value Accessor interface, writeValue. What you'll need to do here is to define what's happening when a new value appears on the FormControl outside. So for example, let's say you have username = FormControl('') on the outside, and your custom form control is handling the username through <app-custom-input [formControl]="username"></app-custom-input>. Now if in the parent component the username is set somehow (e.g. form initialized, form reset, ...), then this function will get triggered. Typically, what you'll want to do, is to write the value to a local variable:

writeValue(input: string) {
  this.input = input;
}

So this means, that anytime the username changes in the parent form control, this function will get triggered with that value and you're writing that value to a local variable. Of course, you could do something else with the value, but that's what you'll do most of the times.

So now that you've defined how a change on the outside is reflected on the inside, you'll have to define how a change on the inside is reflected on the outside. For that you'll listen to changes inside of your custom component as you normally would if this weren't a Control Value Accessor. For example, if it's a button, you're listening to the click, if it's an input, you're listening to keystrokes or input changes or whatever it is that you want to listen to in your custom component. There's one key difference, and that is that you'll have to call this.onChange(...); instead of this.myEventEmitter.emit(...) when you want to inform the outside world about changes. When to call it is up to you, but typically you want to do this at the same point you would have called the .emit. For example, you could leverage this and build an input field, that only updates the form control every 500 milliseconds. That could be useful to not spam servers with request if you have an autocomplete feature or similar.

So in the simplest of cases, you could just trigger the onChange when something in your local form element changes:

<input type="text"
       [ngModel]="input"
       (ngModelChange)="onChange($event)">

Let me reiterate on the point made previously: You don't need to implement onChange yourself! You just need to provide those two lines: onChange: any = () => {} and registerOnChange(fn: any): void {this.onChange = fn;} and then Angular will at some point override your empty onChange implementation with an actually useful one! It's doing this by calling the registerOnChange method, as you can see by looking at that method. That's why you needed so much boilerplate code.

You can view this example here on Stackblitz: https://stackblitz.com/edit/angular-control-value-accessor-simple-example-tsmean

The relevant code is this:
import {Component, OnInit, forwardRef} from '@angular/core';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR} from '@angular/forms';


@Component({
  selector: 'app-custom-input',
  templateUrl: './custom-input.component.html',
  styleUrls: ['./custom-input.component.scss'],

  // Step 1: copy paste this providers property
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomInputComponent),
      multi: true
    }
  ]
})
// Step 2: Add implements ControlValueAccessor
export class CustomInputComponent implements ControlValueAccessor {

  // Step 3: Copy paste this stuff here
  onChange: any = () => {}
  onTouch: any = () => {}
  registerOnChange(fn: any): void {
    this.onChange = fn;
  }
  registerOnTouched(fn: any): void {
    this.onTouch = fn;
  }

  // Step 4: Define what should happen in this component, if something changes outside
  input: string;
  writeValue(input: string) {
    this.input = input;
  }

  // Step 5: Handle "what should happen on the outside, if something changes on the inside"
  // in this simple case, we've handled all of that in the .html
  // a) we've bound to the local variable with ngModel
  // b) we emit to the outside by calling onChange on ngModelChange

}

Alternative: Using a FormControl

You could have gone a slightly different road inside of your CustomInputComponent. Instead of using ngModel, you could have used a form control here as well. In that case, the example would change like this:

Alternative: Using a FormControl (click to show code)

...
export class CustomInputComponent implements ControlValueAccessor, OnDestroy {

  // Step 3: Instead of a simple string variable, use a FormControl
  input = new FormControl('')

  // Step 4: use the setValue method on the form control to update it when a new value comes in from the outside
  writeValue(input: string) {
    this.input.setValue(input);
  }

  // Step 5: Listen to value changes on the form control. If something happens, the function provided by Angular will be called and the outside world will be informed.
  subscriptions = [];
  registerOnChange(fn: any): void {
    this.subscriptions.push(
      this.input.valueChanges.subscribe(fn)
    );
  }
  ngOnDestroy() {
    this.subscriptions.forEach(sub => sub.unsubscribe());
  }
  
  // Step 6: onTouch stays the same
  onTouch: any = () => {}
  registerOnTouched(fn: any): void {
    this.onTouch = fn;
  }

}

and the html

<input type="text"
       [formControl]="input"
       (blur)="onTouch()">

I actually think the alternative approach using a form control could be kind of nice, especially the html template gets simpler. The only problem is that we're introducing a pesky subscription which we'll then have to unsubscribe from in the onDestroy.

More Examples

Lazy Input

A lazy input can be useful, if you want to program something like an autocomplete. Like this, the form control would only be updated once in a while and the server wouldn't be spammed with requests. Here's the code:

Lazy Input Example (click to show code)
import {Component, OnInit, OnDestroy, forwardRef, Input} from '@angular/core';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR} from '@angular/forms';

import {ReplaySubject, Subscription} from 'rxjs';
import {debounceTime} from 'rxjs/operators';

@Component({
  selector: 'app-custom-input',
  templateUrl: './custom-input.component.html',
  styleUrls: ['./custom-input.component.scss'],

  // Step 1: copy paste this providers property
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomInputComponent),
      multi: true
    }
  ]
})
// Step 2: Add "implements ControlValueAccessor"
export class CustomInputComponent implements ControlValueAccessor, OnInit {

  @Input() debounceTime = 500;

  // Step 3: Copy paste this stuff here
  onChange: any;
  onTouch: any;
  registerOnChange(fn: any): void {
    this.onChange = fn;
  }
  registerOnTouched(fn: any): void {
    this.onTouch = fn;
  }

  // Step 4: Define what should happen in this component, if something changes outside
  writeValue(input: string) {
    this.input.next(input);
  }

  // Step 5: Handle what should happen on the outside, if something changes on the inside
  input = new ReplaySubject(1);
  onInputChange(input: string) {
    this.input.next(input);
  }
  subscriptions: Subscription[] = [];
  ngOnInit() {
    this.subscriptions.push(
      this.input
        .pipe(
          debounceTime(1000)
        )
        .subscribe(input => {
          this.onChange(input);
        })
    )
  }
  ngOnDestroy() {
    this.subscriptions.forEach(sub => sub.unsubscribe());
  }

}

Here is the link to the example: Lazy Input Example

Button Example

Another classic in the world of examples is the button increasing a counter. So if your custom form element revolves around a button, choose this as an orientation. Here's the code:

Button Example (click to show code)
import {Component, OnInit, OnDestroy, forwardRef, Input} from '@angular/core';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR} from '@angular/forms';

import {ReplaySubject, Subscription} from 'rxjs';
import {debounceTime} from 'rxjs/operators';

@Component({
  selector: 'app-custom-input',
  templateUrl: './custom-input.component.html',
  styleUrls: ['./custom-input.component.scss'],

  // Step 1: copy paste this providers property
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomInputComponent),
      multi: true
    }
  ]
})
// Step 2: Add "implements ControlValueAccessor"
export class CustomInputComponent implements ControlValueAccessor {

  // Step 3: Copy paste this stuff here
  onChange: any;
  onTouch: any;
  registerOnChange(fn: any): void {
    this.onChange = fn;
  }
  registerOnTouched(fn: any): void {
    this.onTouch = fn;
  }

  // Step 4: Define what should happen in this component, if something changes outside
  writeValue(counter: number) {
    console.log(`writeValue is called with ${counter}.`)
    this.counter = counter;
  }

  // Step 5: Handle what should happen on the outside, if something changes on the inside
  counter: number = 0;
  onClick() {
    this.onChange(++this.counter);
  }
  subscriptions: Subscription[] = [];

}

Link to a Control Value Accessor example with a button: Button example

Nested Forms

Should you use Control Value Accessor for nested forms? The answer is NO, NO, NO! I first made this mistake as well, but if you think about it, it's not at all what the CVA is made for. The CVA is made for scenarios where you wrap a form control in a component, not a form group. If you wrap a form group in a component, then you can just pass in the form group!

Say you have a form that looks like this: {name: string; address: {street: string; zip: number;}}. The address part you deem is worthy of it's own component. Then you can just pass in the sub form into the child component! Here's what the AddressFormComponent would look like:

Nested Forms Example (click to show code)
export class AddressFormComponent {
  @Input() addressForm: FormGroup;
}
<form [formGroup]="addressForm">
  <input type="text" formControlName="street" placeholder="Street">
  <input type="number" formControlName="zip" placeholder="Zip">
</form>
and in the parent component
// app.component.ts
          
  addressForm = this.formBuilder.group({
    street: ["PayneStreet"],
    zip: [50000]
  });

  mainForm: FormGroup = this.formBuilder.group({
    name: ["Max"],
    address: this.addressForm
  });
<!--app.component.html-->
<form [formGroup]="mainForm">
  <input type="text" formControlName="name" placeholder="Name">
  <app-address-form [addressForm]="addressForm"></app-address-form>
</form>

or view this on Stackblitz: Nested Forms Example (you do NOT NEED Control Value Accessor)

Conclusion

Implementing the Control Value Accessor interface isn't rocket science, but since you'll have to add a lot of boilerplate code and still understand the basics of what's going on, it's quite confusing at first. However, I hope I could clear up some of the confusion around Control Value Accessor and illustrate how to use it with some examples!

Dear Devs: You can help Ukraine🇺🇦. I opted for (a) this message and (b) a geo targeted message to Russians coming to this page. If you have a blog, you could do something similar, or you can link to a donations page. If you don't have one, you could think about launching a page with uncensored news and spread it on Russian forums or even Google Review. Or hack some russian servers. Get creative. #StandWithUkraine 🇺🇦
Dear russians🇷🇺. I am a peace loving person from Switzerland🇨🇭. It is without a doubt in my mind, that your president, Vladimir Putin, has started a war that causes death and suffering for Ukrainians🇺🇦 and Russians🇷🇺. Your media is full of lies. Lies about the casualties, about the intentions, about the "Nazi Regime" in Ukraine. Please help to mobilize your people against your leader. I know it's dangerous for you, but it must be done!