Implementing Control Value Accessor in Angular

Majd Asab
5 min readFeb 10, 2019

--

Angular version: 7

Control Value Accessor interface gives us the power to leverage the Angular forms API, and create a connection between it and the DOM element. The major benefits we gain from doing that, is that we get all the default validations you’d get with any element, in order to track the validity, and it’s value.

Learn by example!

The example we’re working with today will teach you how to create a custom input element based component that leverages the forms API using the template driven approach.

For a quick and an easy tutorial, let’s navigate to https://stackblitz.com and create a new Angular project, later on you just have to copy the changes to your local project.

Here’s a screenshot of our file structure for reference:

Initial file structure screenshot

Step 1/3: Create your new component

Start by creating a new component, let’s call it custom-input.component.ts, by default the component will be called CustomInputComponent.

To test if everything is working fine, navigate to custom-input.component.ts, and within the ‘@Component’ annotation change the ‘template’ property value to an input, so the end result for now should look like this:

custom-input.component.ts code

Notice that the file is placed inside ‘custom-input’ folder.

Ensure the the component is imported, and that the imported component name is added to the ‘declaration’ array, similar to this:

app.module.ts configuration

Go ahead and add <app-custom-input></app-custom-input> to your app.component.html and you should see the input element:

screenshot of app.component.html with the input element rendered on the right

Step 2/3: Control Value Accessor prerequisites

Open custom-input.component.ts, and let’s start with the imports:

A. We need to import 1. ‘ControlValueAccessor’ and 2. ‘NG_VALUE_ACCESSOR’ from the forms ngModules, and add ‘forwardRef’ to the list named imports from the core ngModule:

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

B. Within the ‘@Component’ annotation, add the following provider configuration, so that our component gets access to the VALUE_ACCESSOR, in addition update the ‘template’ with the code below to bind a value to the ngModel and output a local value (Don’t worry about any errors for now):

template: '<input [(ngModel)]="value"/>local: {{val}}',
providers: [
{ provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true
}
]

Step 3/3: Implement Control Value Accessor methods

Now it’s time to implement the ControlValueAccessor interface.

You’re going to need to implement the following methods and variables:

  1. onChange → the callback function to register on UI change
  2. onTouch → the callback function to register on element touch
  3. set value(val: any) → sets the value used by the ngModel of the element
  4. writeValue(value: any) → This will will write the value to the view if the the value changes occur on the model programmatically
  5. registerOnChange(fn: any) → When the value in the UI is changed, this method will invoke a callback function
  6. registerOnTouch(fn: any) → When the element is touched, this method will get called

Heres the implementation:

export class CustomInputComponent implements ControlValueAccessor {constructor() { }onChange: any = () => {}onTouch: any = () => {}val= "" // this is the updated value that the class accessesset value(val){  // this value is updated by programmatic changes if( val !== undefined && this.val !== val){this.val = valthis.onChange(val)this.onTouch(val)}}// this method sets the value programmaticallywriteValue(value: any){ this.value = value}// upon UI element value changes, this method gets triggeredregisterOnChange(fn: any){this.onChange = fn}// upon touching the element, this method gets triggeredregisterOnTouched(fn: any){this.onTouch = fn}}

We are done with the implementation. Time to test the validations and the value access!

Test 1/2: Test the value access

Let’s first test if we can bind an ngModel value to the custom component, and track its value.

Navigate to app.component.html and replace what we have before with the following:

<app-custom-input [(ngModel)]="external" name="externalVal"></app-custom-input>external: {{external}}

Make sure you bind ngModel on the custom element to a variable, and provide a name for the component in the name property.

The ‘external: {{external}}’ allows us to view the value which the element holds. Ensure that this variable ‘external’ is declared in app.component.ts

external = ""

If everything goes well without any errors, you should be able to enter values in the input element on the screen and see both values get updated both local and external:

Screenshot of the input element with “works!” as a value, with local and external variables reflecting the input

Test 2/2: Access validations

To access the validations provided to the element by default, simply bind the ngModel to a template reference as follows, and give the reference any name you wish:

<app-custom-input
[(ngModel)]="external"
name="externalVal"
#validations="ngModel"></app-custom-input>

Now to access these validation, simply output the JS-Object and stringify it using the json pipeline operator

<p>{{validations.control | json}}</p>

and….. TA DA!

Screenshot of the validations of the component

You can find a working example here

--

--