How To Create Custom Form Control In Angular

Reading Time: 4 minutes

An Angular’s reactive forms module enables us to build and manage complex forms in our application using a simple but powerful model. You can build your own custom form control that works seamlessly with the Reactive Forms API. The core idea behind form controls is the ability to access a control’s value( form input value). In this blog, I am going to share how can we create custom form control in angular and get value.

Prerequisites

Knowledge of Reactive Form

CONTROLVALUEACCESSOR

Before going to start let’s talk about CONTROLVALUEACCESSOR.

ControlValueAccessor is an interface working as a mediator b/w a FormControl and the native element.

It abstracts the operations of writing a value and listening for changes in the DOM element representing an input control.

interface ControlValueAccessor {
 /**   
 * Write a new value to the element.
 */ 

writeValue(obj: any): void;
 /**
 * Set the function to be called when the control receives a change event.
 */ 

registerOnChange(fn: any): void;
 /**    
 * Set the function to be called when the control receives a touch event.
 */ 

registerOnTouched(fn: any): void;
 /**    
 * This function is called when the control status changes to or from "DISABLED".
 * Depending on the value, it will enable or disable the appropriate DOM element.
 * @param isDisabled
 */

setDisabledState?(isDisabled: boolean): void;
} 

So as you can see in the upper code of the snippet here are 4 methods

  1. writeValue
  2. registerOnChange
  3. registerOnTouched
  4. setDisabledState

Let’s explore one by one every method

writeValue:

Firstly, this method is call for writing a value into a child form control.

implementing writeValue:

writeValue(answer: number) {
    this.answer = answer;
}

registerOnChange:

In the writeValue method, writing a value into a child form control. After this, the most important part is implementing registerOnChange. registerOnChange is a method that registers a handler. When something is triggered in the view means when a user fills the input field this method must call. It gets a function that tells other form directives and forms controls to update their values. 

Implementing registerOnChange:

In the writeValue method parent form can set the value but what about other ways.

If users interact with a form we also need to detect the changes and update the parent form.

For this, child control must notify the parent form that a new value is available via the use of a callback function.

onChange = (number) => {};

registerOnChange(onChange: any) {
   this.onChange = onChange;
}

As we can see, when this method is called, we are going to receive our callback function, which we then save for later in a member variable.

The onChange member variable is declared as a function, and initialized with an empty function, meaning a function with an empty body.

This way, if our program by some reason calls the function before the registerOnChange the call was made, we won’t run into any errors.

registerOnTouched:

Update the new value of the parent form, we must also notify the parent form when child control was considered to be affected by the user.

We need to have a callback registered so that the child control can report its touched status back to the parent form:

implementing registerOnTouched

registerOnTouched(onTouched: any) {
  this.onTouched = onTouched;
}

setDisabledState:

Update the new value of the parent form, we must also notify the parent form when child control was considered to be affected by the user.

implemetnting setDisabledState:

disabled: boolean = false;
SetDisabledState(disabled: boolean) {
  this.disabled = disabled;
}

Dependency injection configuration for ControlValueAccessor

To implement the ControlValueAccessor interface correctly, is to register the custom form control as a known value accessor in the dependency injection system:

@Component({
    selector: 'app-text-input',
    templateUrl: './text-input.component.html',
    providers: [{
        provide: NG_VALUE_ACCESSOR,
        multi: true,
        useExisting: forwardRef(() => TextInputComponent)
    }]
})

export class TextInputComponent implements ControlValueAccessor{ }

Without this configuration in place, our custom form control will not work correctly.

Now, By this, we are adding the component to the list of known value accessors, that are all registered with the NG_VALUE_ACCESSOR unique dependency injection key (also known as an injection token).

Notice the multi flag set to true. This means that this dependency provides a list of values, and not only one value. This is normal because there are many value accessors registered with Angular Forms besides our own.

Now Let’s Create a custom form control and play with it.

I am going to create Text-input-component.

@Component({
    selector: 'app-text-input',
    templateUrl: './text-input.component.html',
    providers: [{
        provide: NG_VALUE_ACCESSOR,
        multi: true,
        useExisting: forwardRef(() => TextInputComponent)
    }]
})

export class TextInputComponent implements AfterViewInit, ControlValueAccessor, DoCheck {
    @Input() config: TextInputConfigModel;
    @Input() verificationPending: boolean;
    @Input() errors: any;
    @Input() valid: boolean;
    @Input() isDisabled = false;
    @Input() autocapitalize = false;
    oldValue ='';
    inputField = new FormControl('');
    errorMessages = []
    private onChange: (value: string) => void;
    private onTouched: () => void;

    ngAfterViewInit() {
        this.inputField.valueChanges.subscribe(value => {
           this.onChange(value.trim());
            }  
        });
        this.setDisabledState(this.isDisabled);
    }

    ngDoCheck() {
        if (this.valid) {
            this.inputField.setErrors(null);
            this.errorMessages = []
        } else {
            this.inputField.setErrors(this.errors);
            this.errorMessages = getErrorMessages(this.config.errorMessages, this.errors);
        }
    }

    writeValue(obj: any) {
        this.inputField.setValue(obj);
    }

    registerOnChange(fn: any) {
        this.onChange = fn;
    }

    registerOnTouched(fn: any) {
        this.onTouched = fn;
    }

    setDisabledState(isDisabled: boolean) {
        if (isDisabled) {
            this.inputField.disable();
        } else {
            this.inputField.enable();
        }
    }

    handleOnBlur() {
        this.onTouched();
    }

}

export interface TextInputConfigModel {
    label: string,
    placeholder?: string,
    type?: string,
    hint?: string,
    showValidTick?: boolean;
    showInvalidCross?: boolean;
    errorMessages?: any;
}

In the above code, you can easily understand what we are doing. I have written this code for also handling error parts. In the ngDoCheck method, we are checking the validity for our control and displaying errors.

Summary

To sum up, in short, we learn

  • implementation of CONTROLVALUEACCESSOR.
  • utilization of CONTROLVALUEACCESSOR methods.
  • How can we create custom form control.

In conclusion, creating a custom control is very help full. Sometimes we need to create a dynamic form. Implementing custom control plays a vital role in such situations.