DEV Community

krishna
krishna

Posted on

Understanding angular ControlValueAccessor with an example

In my previous article, I explained the ControlValueAccessor Interface now lets deep dive with an example.

Let’s say you have a custom input component that is meant to accept phone numbers in a specific format, such as (123) 456–8799. You want to create a custom form control that will enforce this format and allow for easy data binding to the component.

To achieve this, you can create a ControlValueAccessor for the custom input component. The ControlValueAccessor interface defines four methods that you need to implement: writeValue(), registerOnChange(), registerOnTouched(), and setDisabledState().

Here’s an example implementation of the ControlValueAccessor interface for our custom phone number input component:

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

@Component({
  selector: 'app-phone-number-input',
  templateUrl: './phone-number-input.component.html',
  styleUrls: ['./phone-number-input.component.css'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => PhoneNumberInputComponent),
      multi: true
    }
  ]
})
export class PhoneNumberInputComponent implements ControlValueAccessor {
  myForm! : FormGroup;
  private onChange: (value: string) => void;
  private onTouched: () => void;

  public phoneNumber: string;

   constructor(private fb: FormBuilder) { 
    this.myForm = this.fb.group({
       // implement the formcontrol
    })
   }
  public writeValue(value: string): void {
    this.phoneNumber = value;
  }

  public registerOnChange(fn: (value: string) => void): void {
    this.onChange = fn;
  }

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

  public setDisabledState?(isDisabled: boolean): void {
    // Implement if needed
  }

  public onPhoneNumberChanged(): void {
    // enforce the phone number format
    const formattedPhoneNumber = this.phoneNumber.replace(/[^0-9]/g, '').replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
    this.phoneNumber = formattedPhoneNumber;

    // notify the form control that the value has changed
    this.onChange(formattedPhoneNumber);
  }

  public onBlur(): void {
    this.onTouched();
  }
}
Enter fullscreen mode Exit fullscreen mode

In the above code, we first import ControlValueAccessor and NG_VALUE_ACCESSOR from the @angular/forms module. We then implement the ControlValueAccessor interface in our PhoneNumberInputComponent class.

The writeValue() method is called by the Angular forms API when the value of the form control is updated. We simply set the phoneNumber property to the value passed in.

The registerOnChange() and registerOnTouched() methods are used to register callback functions that Angular calls when the value or touched state of the control changes. We store these callback functions in private class properties onChange and onTouched.

The setDisabledState() method is optional and can be implemented if you want to disable the input control.

The onPhoneNumberChanged() method is called whenever the phone number input value changes. We enforce the phone number format by replacing all non-numeric characters and formatting the string with parentheses and hyphens. We then call the onChange() method with the formatted phone number to notify the form control that the value has changed.

The onBlur() method is called whenever the input control loses focus. We call the onTouched() method to notify the form control that the input has been touched.

By implementing the ControlValueAccessor interface, our custom phone number input component can be easily used in a reactive form. In the above code, we first import ControlValueAccessor and NG_VALUE_ACCESSOR from the @angular/forms module. We then implement the ControlValueAccessor interface in our PhoneNumberInputComponent class.

The writeValue() method is called by the Angular forms API when the value of the form control is updated. We simply set the phoneNumber property to the value passed in.

The registerOnChange() and registerOnTouched() methods are used to register callback functions that Angular calls when the value or touched state of the control changes. We store these callback functions in private class properties onChange and onTouched.

The setDisabledState() method is optional and can be implemented if you want to disable the input control.

The onPhoneNumberChanged() method is called whenever the phone number input value changes. We enforce the phone number format by replacing all non-numeric characters and formatting the string with parentheses and hyphens. We then call the onChange() method with the formatted phone number to notify the form control that the value has changed.

The onBlur() method is called whenever the input control loses focus. We call the onTouched() method to notify the form control that the input has been touched.

By implementing the ControlValueAccessor interface, our custom phone number input component can be easily used in a reactive form.

And in the template, you will create a form with FormGroup where you will specify the component and use the app-phone-number-input like the below example.

<form [formGroup]="myForm">
  <label for="phone">Phone number:</label>
  <app-phone-number-input formControlName="phone"></app-phone-number-input>
</form>
Enter fullscreen mode Exit fullscreen mode

In the above code, we first create a FormGroup in our component's class and assign it to the formGroup attribute of the <form> element.

We then create a label for the phone number input and use the formControlName directive to bind the phone number input to the "phone" form control.

Finally, we use the <app-phone-number-input> custom input component and pass it the "phone" form control.

Now, whenever the phone number input value changes, the onPhoneNumberChanged() method in our PhoneNumberInputComponent will be called, and the value will be propagated to the "phone" form control through the onChange() callback.

Similarly, whenever the input loses focus, the onBlur() method will be called, and the onTouched() callback will be called to mark the control as touched.

Note that you also need to import the ReactiveFormsModule in your module in order to use reactive forms in your application

Top comments (0)