How To Build a Custom Angular FormControl

Create Masterful Angular Forms with ControlValueAccessor

·

3 min read

How To Build a Custom Angular FormControl

Master Your Forms and Become an Angular Sensei (đź“· Monkey_Zen Studio)

“Angular Forms are a vital tool in the web developer’s arsenal, providing the power to seamlessly manage user input and validation with grace and ease.” — SenseiGPT

Forms đź“‹

With Angular Forms, web developers gain access to a wealth of benefits that make handling user input a breeze. Angular makes it easy to create dynamic forms, validate user input, and access form properties such as touched, valid, and submitted. With the release of Angular 14, forms have become type-safe from tip to toe and can be used to easily implement complicated input schemes. Forms are a powerful tool for handling user input easily and effectively.

This article demonstrates how to use ControlValueAccessor to create a custom component that can be used in a reactive form. A sample implementation of a custom FormControl can be found via the GitHub link below.

Begin 🤺

Developers can bind reactive forms to input, radio, and select elements via the FormControlName directive. Angular also provides tooling that allows developers to create easily new FormControls specific to the needs of their applications via ControlValueAccessor.

To get started, create a new component via the Angular CLI.

ng g c ngx-toggle

Add a provider for NG_VALUE_ACCESSOR, with an ExistingProvider that utilizes a forwardRef to the component’s @Component decorator.

@Component({
    selector: 'ngx-toggle',
    templateUrl: './ngx-toggle.component.html',
    styleUrls: ['./ngx-toggle.component.scss'],
    providers: [{
        provide: NG_VALUE_ACCESSOR,
        useExisting: forwardRef(() => NgxToggleComponent),
        multi: true
    }]
})
export class NgxToggleComponent {
    ...
}

Update the component’s class definition so that it implements ControlValueAccessor.

export class NgxToggleComponent implements ControlValueAccessor {
    ...
}

Implement the ControlValueAccessor interface by defining the functions writeValue, registerOnChange, registerOnTouched, and (optionally) setDisabledState. By implementing the interface using the VS Code Intellisense suggestion we get the following:

registerOnChange(fn: any): void {
    throw new Error('Method not implemented.');
}

registerOnTouched(fn: any): void {
    throw new Error('Method not implemented.');
}

setDisabledState?(isDisabled: boolean): void {
    throw new Error('Method not implemented.');
}

writeValue(obj: any): void {
    throw new Error('Method not implemented.');
}

Let’s add some types and implement the interface properly.

export class NgxToggleComponent implements ControlValueAccessor {
    @Input() id = 'ngx-toggle';
    @Input() checked = false;
    @Input() disabled = false;
    @Output() checkedChange = new EventEmitter<boolean>();

    onChange: OnChangeFn = () => {};
    onTouched: OnTouchedFn = () => {};

    onClick(event: MouseEvent): void {
        const checked = (event.target as HTMLInputElement).checked;
        this.onChange(checked);
        this.onTouched();
        this.checkedChange.emit(checked);
    }

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

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

    setDisabledState(isDisabled: boolean): void {
        this.disabled = isDisabled;
    }

    writeValue(checked: boolean): void {
        this.checked = checked;
        this.checkedChange.emit(checked);
    }
}

type OnChangeFn = (value: boolean) => void;
type OnTouchedFn = () => void;

Angular calls registerOnChange and registerOnTouched with a callback function that allows the component to notify Angular when the component is changed or touched respectively. When the user interacts with your component it should call onTouched. Similarly, when your component’s value changes you should call onChange and pass the new value. When the bound form’s value changes, Angular invokes writeValue which allows the component to update its internal state accordingly. Finally, setDisabledState is called by Angular when the FormControl is disabled and gives the developer a place where they can optionally disable the custom FormControl.

Verification âś…

You can now use the custom FormControl in your application’s reactive forms, let’s test it out!

Declare a FormGroup in one of your components.

formGroup = new FormGroup({
    saveForNextTime: new FormControl(false)
});

Create an instance of <ngx-toggle> in your template and inside of a <form> element and provide a value for formControlName.

<form [formGroup]="formGroup">
  <label for="save-for-next-time">Save this information for next time?</label>
  <ngx-toggle id="save-for-next-time" formControlName="saveForNextTime"></ngx-toggle>
</form>

Forms Ninja 🥷

That’s it — you’ve successfully created a custom Angular FormControl! You're one step closer to mastery and can now use almost any component in a form — well done!

Want to Connect?
If you found the information in this tutorial useful please subscribe on Hashnode, follow me on Twitter, and/or subscribe to my YouTube channel.

Â