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:
- 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.