--- { title: "Make Better Angular Form Components using ControlValueAccessor", description: "You may have ran into elements or components that allow you to use formControl or ngModel. They make your life as a consumer much easier. Let's build one!", published: '2020-06-09T13:45:00.284Z', authors: ['crutchcorn'], tags: ['angular', 'javascript'], attached: [], license: 'cc-by-nc-sa-4' } --- One of Angular's greatest strengths over its contemporaries like React or Vue is that it's a framework. What does this mean in the practical sense? Well, because you're providing the defaults for everything right out-of-the-box, you have a set of guard rails to follow when architecting new things. A set of baseline rules for things to follow, so to speak. One such guard rail comes in the form of the `@angular/forms` package. If you've used Angular for long, you're doubtlessly familiar with [the `[(ngModel)]` method of two-way data binding in the UI](https://angular.io/guide/forms#two-way-data-binding-with-ngmodel). Seemingly all native elements have support for this feature (so long as you have `FormsModule` imported in your module). More than that, if you want more powerful functionality, such as disabling an entire form of fields, tracking a collection of fields in a form, and doing basic data validation, [you can utilize Angular Reactive Forms' `[formControl]`](https://angular.io/guide/reactive-forms#adding-a-basic-form-control) and do all of that and more. These features are hugely helpful when dealing with complex form logic throughout your application. Luckily for us, they're not just exclusive to native elements - we can implement this functionality into our own form! # Example {#code-demo} It's hard for us to talk about the potential advantages to a component without having actually taken a look at it. Let's start with this component, just for fun. It'll allow you to type in data, have a header label (as opposed to a floating label, [which is notoriously bad for A11Y](https://www.matsuko.ca/blog/stop-using-material-design-text-fields/)), and even present a fun message when "Unicorns" is typed in. Here's the code: ```typescript import { Component, Input } from "@angular/core"; @Component({ selector: "app-example-input", template: `
{{ isSecretValue ? "You discovered the secret unicorn rave! They're all having a party now that you summoned them by typing their name" : "" }}
`, styleUrls: ["./example-input.component.css"] }) export class ExampleInputComponent { @Input() placeholder: string; value: any = ""; get isSecretValue() { return /unicorns/.exec(this.value.toLowerCase()); } } ``` With only a bit of CSS, we have a visually appealing, A11Y friendly, and quirky input component. Look, it even wiggles the unicorns! Now, this component is far from feature complete. There's no way to `disable` the input, there's no way to extract data out from the typed input, there's not a lot of functionality you'd typically expect to see from an input component. Let's change that. # ControlValueAccessor {#intro-concept} Most of the expected form functionality will come as a complement of [the `ControlValueAccessor` interface](https://angular.io/api/forms/ControlValueAccessor). Much like you implement `ngOnInit` by implementing class methods, you do the same with ControlValueAccessor to gain functionality for form components. The methods you need to implement are the following: - `writeValue` - `registerOnChange` - `registerOnTouched` - `setDisabledState` Let's go through these one-by-one and see how we can introduce change to our component to support each one. ## Setup {#forwardRef} In order to use these four methods, you'll first need to `provide` them somehow. To do this, we use a combination of the component's `providers` array, `NG_VALUE_ACCESSOR`, and `forwardRef`. ```typescript import { forwardRef } from '@angular/core'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; /** * Provider Expression that allows your component to register as a ControlValueAccessor. This * allows it to support [(ngModel)] and ngControl. */ export const EXAMPLE_CONTROL_VALUE_ACCESSOR: any = { /** * Used to provide a `ControlValueAccessor` for form controls. */ provide: NG_VALUE_ACCESSOR, /** * Allows to refer to references which are not yet defined. * This is because it's needed to `providers` in the component but references * the component itself. Handles circular dependency issues */ useExisting: forwardRef(() => ExampleInputComponent), multi: true }; ``` Once we have this example provide setup, we can now pass it to a component's `providers` array: ```typescript @Component({ selector: 'app-example-input', templateUrl: './example-input.component.html', styleUrls: ['./example-input.component.css'], providers: [EXAMPLE_CONTROL_VALUE_ACCESSOR] }) export class ExampleInputComponent implements ControlValueAccessor { ``` With this, we'll finally be able to use these methods to control our component. > If you're wondering why you don't need to do something like this with `ngOnInit`, it's because that functionality is baked right into Angular. Angular _always_ looks for an `onInit` function and tries to call it when the respective lifecycle method is run. `implements` is just a type-safe way to ensure that you're explicitly wanting to call that method. ## `writeValue` {#write-value} `writeValue` is a method that acts exactly as you'd expect it to: It simply writes a value to your component's value. Because of this, it's suggested to have a setter and getter for `value` and a private internal value that's used as the real value: ```typescript private _value: any = null; @Input() get value(): any { return this._value; } set value(newValue: any) { if (this._value !== newValue) { // Set this before proceeding to ensure no circular loop occurs with selection. this._value = newValue; } } ``` Once this is done, the method is trivial to implement: ```typescript writeValue(value: any) { this.value = value; } ``` However, you may notice that your component doesn't properly re-render when you update your value from the parent component. Because you're updating your value outside of the typical pattern, change detection may have a difficult time running when you'd want it to. To solve for this, provide a `ChangeDetectorRef` in your constructor and manually check for updates in the `writeValue` method: ```typescript export class ExampleInputComponent implements ControlValueAccessor { // ... constructor(private _changeDetector: ChangeDetectorRef) { } // ... writeValue(value: any) { this.value = value; this._changeDetector.markForCheck(); } ``` Now, when we use a value like `new FormValue('test')` and pass it as `[formControl]` to our component, it will render the correct default value ## `setDisabledState` {#disabled-state} Implementing the disabled state check is extremely similar to [implementing value writing](#write-value). Simply add a setter, getter, and `setDisabledState` to your component and you should be good-to-go: ```typescript private _disabled: boolean = false; @Input() get disabled(): boolean { return this._disabled; } set disabled(value) { this._disabled = coerceBooleanProperty(value); } setDisabledState(isDisabled: boolean) { this.disabled = isDisabled; this._changeDetector.markForCheck(); } ``` Just as we did with value writing, we want to run a `markForCheck` to allow change detection to work as expected when the value is changed from a parent > It's worth mentioning that unlike the other three methods, this one is entirely optional for implementing a `ControlValueAccessor`. This allows us to disable the component or keep it enabled, but is not required for usage with the other methods. `ngModel` and `formControl` will work without this method implemented. ## `registerOnChange` {#register-on-change} While the previous methods have been implemented in a way that required usage of `markForCheck`, these last two methods are implemented in a bit of a different way. You only need look at the type of the methods on the interface to see as much: ```typescript registerOnChange(fn: (value: any) => void); ``` As you might be able to deduce from the method type, when `registerOnChange` is called, it passes you a function. You'll then want to store this function in your class instance and call it whenever the user changes data. ```typescript /** The method to be called in order to update ngModel */ _controlValueAccessorChangeFn: (value: any) => void = () => {}; registerOnChange(fn: (value: any) => void) { this._controlValueAccessorChangeFn = fn; } ``` While this code sample shows you how to store the function, but doesn't outline how to call it once stored. You'll want to make sure to call it with the updated value on every update. For example, if you are expecting an `input` to change, you'd want to add it to `(change)` output of the `input`: ```html ``` ## `registerOnTouched` {#register-on-touched} Likewise to how you [store a function and call it to register changes](#register-on-change), you do much of the same to register when a component has been "touched" or not. This tells your consumer when a component has had interaction or not. ```typescript onTouched: () => any = () => {}; registerOnTouched(fn: any) { this.onTouched = fn; } ``` You'll want to call this `onTouched` method any time that your user "touches" (or, interacts) with your component. In the case of an `input`, you'll likely want to place it on the `(blur)` output: ```html ``` # Consumption {#consume-demo} Now that we've done that work, let's put it all together, apply [the styling from before](#code-demo), and consume the component we've built! We'll need to start by importing `FormModule` and `ReactiveFormModule` into your `AppModule` for `ngModel` and `formControl` support respectively. ```typescript import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { ExampleInputComponent } from './example-input/example-input.component'; @NgModule({ imports: [ ReactiveFormsModule, FormsModule, BrowserModule ], declarations: [ AppComponent, ExampleInputComponent ], bootstrap: [ AppComponent ] }) export class AppModule { } ``` Once you have support for them both, you can move onto adding a `formControl` item to your parent component: ```typescript import { Component, VERSION } from '@angular/core'; import {FormControl} from '@angular/forms'; @Component({ selector: 'my-app', templateUrl: './app.component.html', styleUrls: [ './app.component.css' ] }) export class AppComponent { control = new FormControl(''); modelValue = ""; } ``` Finally, you can pass these options to `ngModel` and `formControl` (or even `formControlName`) and inspect the value directly from the parent itself: ```htmlThe value of the input is: {{control.value}}
The value of the input is: {{modelValue}}
``` If done properly, you should see something like this: # Form Control Classes Angular CSS masters might point to [classes that's applied to inputs when various state changes are made](https://angular.io/api/forms/NgControlStatus#css-classes-applied). These classes include: - `ng-pristine` - `ng-dirty` - `ng-untouched` - `ng-touched` They reflect states so that you can update the visuals in CSS to reflect them. When using `[(ngModel)]`, they won't appear, since nothing is tracking when a component is `pristine` or `dirty`. However, when using `[formControl]` or `[formControlName]`, these classes _will_ appear and act accordingly, thanks to the `registerOnChange` and `registerOnTouched` functions.