Let’s assume we create a custom (angular) Component “custom-select” in a reactive form and want to use it like below:

<div [formGroup]="filterFormControls">
   <custom-select [items]="selectItems" formControlName="country"></custom-select>
</div>

If we yet try to transpile the code, we will get the following errors : “No value accessor for form control with name: ‘country

That’s happens because value accessor need to be implemented first in custom component. Control Value Accessor interface gives us the power to leverage the Angular forms API, and create a connection between it and the DOM element.

Here are the STEPS:

  1. Add the provider for NG_VALUE_ACCESSOR in the decorator
 import ...
        import { Component, forwardRef } from '@angular/core';
        import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
        
    
        @Component({

            selector: 'custom-select',
            templateUrl: 'custom-select.component.html',
            styleUrls: ['custom-select.component.css'],
 // STEP 1
            providers: [{
              provide: NG_VALUE_ACCESSOR,
              multi: true,
              useExisting: forwardRef(() => CustomSelectComponent)
            }]
        })

2. Implement the ControlValueAccessor in the class

// STEP 2
 export class CustomSelectComponent implements ControlValueAccessor {
        // write code here   .....
 }

Now it’s time to implement the ControlValueAccessor interface.

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

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

3. Create the onChange function


export class CustomSelectComponent implements ControlValueAccessor {
     ... write code here   .....
 // STEP 3 : onChange function
   propagateChange = (_: any) => {
};
 }

4. Add the registerOnChange, writeValue and registerOnTouched methods of the ControlValueAccessor interface

export class CustomSelectComponent implements ControlValueAccessor {
     ... write code here   .....
   //onChange function
   propagateChange = (_: any) => {
};


// STEP 4
   registerOnChange(fn: (_: any) => void) {
       this.propagateChange = fn;
   }
   writeValue() {}
   registerOnTouched(){}
 }

5. In the method that will changed the value of select, call the onChange function passing as parameter the new-value of select.


export class CustomSelectComponent implements ControlValueAccessor {
        // write code here   .....

// STEP 5
   onSelectionChange(event: any) {
       this.propagateChange(event.value);
   }
 }

6. Implement the value setter & writeValue Abstract function

//STEP 6

val= "" // this is the updated value that the class accesses. We can also use a formControl // for example in case of a custom selectBox,we could instead use 
//selectCtrl: FormControl = new FormControl();
set value(val:any){
    if( val !== undefined && this.val !== val){
      this.val = val; // in case of selectBox: this.selectCtrl.setValue(val);
      this.propagateChange(val)
    }
 }

// this method sets the value programmatically
writeValue(value: any){ 
  this.value = value; //In case of selectBox: this.selectCtrl.setValue(value);
}

In the HTML :

<div [formGroup]="filterFormControls">
   <custom-select [items]="selectItems" formControlName="country"  
     (change)="onSelectionChange($event)">
   </custom-select>
</div>

Now, if we transpile the code again…everything will work fine. The formControlName ist binded in component.

By Shabazz

Software Engineer, MCSD, Web developer & Angular specialist

Leave a Reply

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