Building a Reusable Form Control
This guide walks through the creation of a custom form control that plugs into Angular's forms API, working seamlessly with both template-driven and reactive forms just like a built-in control such as <input type="text" ../>.
We'll implement a straightforward color picker component and convert it into a reusable form control. Once finished, you can drop this control into any Angular form setup like so:
Template-driven forms:
<color-picker [(ngModel)]="color"></color-picker>
Reactive (model-driven) forms:
<color-picker [formControl]="color"></color-picker>
Alternatively:
<color-picker formControlName="color"></color-picker>
The complete source code is available in this StackBlitz project, or embedded at the end of this article.
https://stackblitz.com/edit/custom-form-field-color-picker?embed=1&file=src/app/app.component.html
Setting Up the Component
Start by defining a basic component with the following structure:
This component is fairly straightforward:
- It maintains an array called
colorswith predefined color values. The template loops over this list, rendering adivfor each color, styled with that color as its background. - A property named
selectedColorstores the currently chosen color. - Clicking a color triggers the
colorClickedmethod, which updates theselectedColorproperty. - The template applies the CSS class
selectedto the div representing the active color.
As it stands, this component is functional but isolated. It has no mechanism to notify the surrounding form about changes to the selected color, nor can the form tell the component which color should be selected. To bridge this gap, we need to convert it into a proper Angular form control. That requires two key steps:
- Implement the
ControlValueAccessorinterface so the component behaves as Angular's forms module expects. - Register the component with Angular's forms system by providing it through the
NG_VALUE_ACCESSORinjection token.
Transforming Our Component into a Valid Angular Custom Form Control
1- Implementing the ControlValueAccessor Interface
To allow Angular's forms API to communicate with our custom form control, we must implement the ControlValueAccessor interface. If you inspect Angular's source code on GitHub here, you'll find this explanation of the ControlValueAccessor interface:
- Defines an interface that acts as a bridge between the Angular forms API and a native element in the DOM.
* Implement this interface to create a custom form control directive * that integrates with Angular forms.
This interface comprises several methods, each of which we'll implement in our component:
- WriteValue: The forms API calls this method whenever the model value associated with this control changes programmatically. In essence, this is Angular notifying our component that the form value has been altered, and we need to respond accordingly. The method provides the new value via its sole parameter
obj, and we must update the UI to reflect it. In our case, we simply assign the new value to theselectedColorproperty of the color picker component.
writeValue(obj: any): void {
this.selectedColor = obj;
}
- registerOnChange: This method offers a channel for communication in the reverse direction. Whereas writeValue informs our component about changes from the external form, we now need a mechanism to inform the external form about changes originating from our component's UI—in this scenario, when the user selects a new color. This method supplies a callback function
fnin its parameter, which we should invoke whenever a UI change occurs. To do this, we store the callback in a variable and call it each time the user picks a different color.
private _onChange: any;
registerOnChange(fn: any): void {
this._onChange = fn; // Save the callback function
}
colorClicked(color: string) {
this.selectedColor = color;
this._onChange(this.selectedColor); // Call the saved callback
}
- registerOnTouched: This method functions similarly to
registerOnChange; it provides a callback to notify the form when the control has been touched. Typically, with an input field, you'd call this callback on blur. For our example, we consider the control touched once a new color is selected.
private _onTouch: any;
registerOnTouched(fn: any): void {
this._onTouch = fn; // Save the callback function
}
colorClicked(color: string) {
this.selectedColor = color;
this._onTouch(true); // Call the saved callback
}
- setDisabledState: This is the final method to implement. The forms API invokes it whenever the control's disabled status changes. We're expected to react by disabling color selection in our component, so we'll store the value passed to this method each time it's called.
private _isDisabled: boolean;
setDisabledState?(isDisabled: boolean): void {
this._isDisabled = isDisabled;
}
2- Registering Our Component with the NG_VALUE_ACCESSOR Injection Token
Our component is now prepared to work with Angular's forms API. However, one additional step is required to enable the forms API to recognize it as a legitimate form control and interact with it—this interaction is possible only because we implemented the ControlValueAccessor interface in the preceding step.
Let's first examine the source code for Angular's official FormControlDirective, which is responsible for associating our component with the form, to understand how this connection is established. Looking at the directive's constructor, we find the following:
constructor( ...
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[],
...) {...
selectValueAccessor(this, valueAccessors);
}
Notice that the directive injects the NG_VALUE_ACCESSOR token and expects it to supply a list of ControlValueAccessor implementations (the interface we just implemented). This value is then stored and used internally.
What does this imply for us? It means that for FormControlDirective to acknowledge our component and interact with it, we must register our component using the NG_VALUE_ACCESSOR injection token. To accomplish this, we need to modify the options of the Component decorator as shown below:
- We configure the component's injector with the
NG_VALUE_ACCESSORinjection token. - We then provide our newly created
ColorPickerComponent. - Next, we employ
forwardRef(learn more about forwardRef) because our class isn't defined at this point; this function allows us to reference our component before its definition. - Finally, we set
multi:trueto indicate this is one of potentially many providers for the same token on the same element. This is also essential because it ensures the injector returns an array of instances, which is precisely the type thatFormControlDirectiveexpects in its constructor.
Our custom form control is now ready for use in both template-driven and reactive forms. For instance, we could use it in our AppComponent like this:
- We define a
formGroupcontaining two controls, title and color, and we add an HTML form element with theformGroupdirective. - The title control uses a simple native input, while the color control uses our newly created color picker component.
- We use
formControlNameto bind these controls to our form. - Finally, we output the form's value to verify that everything operates correctly when we modify the form input values.
After applying some styling, the final outcome looks like this:
Happy coding, and always keep exploring!
References
forwardReffunction, Angular official documentation https://angular.io/api/core/forwardRef.ClassProvider, Angular official documentation https://angular.io/api/core/ClassProvider.- Basics of reactive forms, Angular official documentation https://angular.io/guide/reactive-forms.

