How to make custom Angular Components Form Enabled & ngModel Enabled?

Learning & implementing ControlValueAccessor in custom components

Posted by Anas R Firdousi on 1st July , 2017
12 mins read

How to make custom Angular Components Form Enabled & ngModel Enabled?

Component is one of the essential building block of any Angular app. A component controls a patch of screen called a view. Components are the main way we build and specify elements and logic on the page, through both custom elements and attributes that add functionality to our existing components. When we build custom components in Angular, we try to make them reusable across several views. Generally, Angular developers like to divide components into two different types: dumb vs logical components. Dumb components are display-only components. They expect inputs, and show them in template attached to that component. Logical component not only display data, but also contains some logic. They may talk to a service to get values via http or they may perform some general validations. Bottom line, logical components do a little more heavy lifting than dumb components.

In this article, I would like to discuss another way of classifying components. Components that are intended to be used within a form and components that are to be used outside of any form context. What's the difference you ask? We may want to fetch values out of a component inside of a form context. Here is a starting point:

What we have right now is 2 custom components custom-input and custom-range components and a main app-component which hosts the two. Nothing fancy here. Here is how the custom-input component looks like at this time: (Not putting custom-range code but you can look into Plnkr)

Also the app component's template consuming these components have nothing special either:

Note that I have also kept a simple HTML checkbox which is not a custom component. This is for a reason and we will get to that in a bit. So far so good. Now the plan is to make the two custom components form enabled & ngModel enabled so that they become consumable in both reactive/modal driven and template driven forms.

Angular 2+ supports two types of forms. Reactive(Modal Driven) Forms & Template Driven forms. Reactive forms are synchronous. Template-driven forms are asynchronous. It's a difference that matters. If you are new to Angular, you can read the basics of these two approaches here and here.

Let's do a fail experiment

Let's turn our <form> into a Reactive Angular 2 form and see what happens. To setup a reactive form, I am going to do the following:

  • import ReactiveFormsModule from @angular/forms and add it into imports of the main @NgModule declaration. This will make all Angular machinery needed to make reactive forms across all components within the module.
  • import FormControl and FormGroup from @angular/forms in main app component.
  • Create a formGroup object inside the app component and three properties, 2 for our custom components and 1 to read values from the generic checkbox we are using inside of the form.
  • Add [formGroup] to the <form> tag and specify the formGroup name. Also add formControlName attribute to each component and specify a name too. This will keep form in template sync with form declaration in app component .ts file.

Too much theory? Let's look at code snippets and also at a final Plnkr (which we predict will break ;) )

Changes in @NgModule look like:

And after making all other changes to app.component.ts, it looks like this:

And a updated (broken) Plnkr is here:

Result? A very interesting failure! The beautiful red console reveals:

The interesting part is there is no error for the generic checkbox we added. Why? Because Angular form knows how to read/extract value from all generic HTML input elements. Angular by default has implemented something called ControlValueAccessor for all basic inputs. Think of ControlValueAccessor as a bridge between a form control and native element. ControlValueAccessor makes sure that changes in custom component are propagated from the model to the view, and also from the view, back to the model. A ControlValueAccessor abstracts the operations of writing a new value to a DOM element representing an input control. ControlValueAccessor is basically an interface and any custom component who would like to add the capability of becoming usable inside of a Form in Angular, needs to implement the ControlValueAccessor interface. Here is how the ControlValueAccessor interface inside Angular core looks like:

The first question is why Angular provides this interface? The reason is simple, every DOM input element differs in the way it sets and gets values internally. For example, radio button and checkbox have checked as well as value property which needs to updated and also read values from. Where as a text box only has a value property that needs to be updated. Angular provides a ControlValueAccessor for every basic input type which knows how to update its view as well as its state.

The next question, what does each of these ControlValueAccessor interface members are meant for?

All the methods, writeValue, registerOnChange, registerOnTouched and setDisabledState are methods automatically called by Angular for each component implementing ControlValueAccessor interface.
  • writeValue(obj: any): void writes new values from the form model into the view or any DOM property. writeValue is a function where you would want to update the model/state used in the view or by your form in your component code.
  • registerOnChange(fn: any): void is used to register a handler that should be fired when something in the view has changed. Just for experiment, let's see what function Angular automatically throws in the argument of registerOnChange(): resulted in This in internal Angular framework code. It gets a function that tells other form directives and form controls to update their values. In other words, that’s the handler function we want to call whenever counterValue changes through the view.
  • registerOnTouched(fn: any): void Similiar to registerOnChange(), this registers a handler specifically for when a control receives a touch event.
  • setDisabledState(isDisabled: boolean): void is used to mark a custom control as disabeled. This can come in handy if we want to ignore a value within a form.

Our next step, naturally, is to implement ControlValueAccessor in our custom components. Following is a list of steps that we will have to take for both CustomInput and CustomRange in order to successfully implement ControlValueAccessor.

  • Import ControlValueAccessor and NG_VALUE_ACCESSOR from @angular/forms
  • Add implements ControlValueAccessor on class.
  • Provide implementation for writeValue(value), registerOnChange(fn), registerOnTouched(fn) and setDisabledState(isDisabled). setDisabledState is not mandatory.
  • Only implementing the interface is not enough. It's 80% of the work but there is still one important piece missing. Interface is a TypeScript construct. Browsers still only understand ES5. If you know TypeScript well, you already know that after transpilation, TypeScript spits nothing for interface. It is just a development time construct. What this means is we still have to explicitly define providers. What is a provider? A provider is an instruction that describes how an object for a certain token is created. If you have used Services in Angular 2+, you have already used providers. In any component within the @Component decorator, you may have added the provider property like providers: [DataService]. This allows the component to then make use of DataService by "providing" a new instance of 'DataService' to this component. You can write the exact same property in explicit form as providers: [ provide: DataService, useClass: DataService ] which explicitly defines which kind of object to provide to this component when it asks for 'DataService'.

    To learn more about dependency injection in Angular 2+ and how providers work, you can read Dependency Injection Engine in Angular 2 - First look You can also refer to Pascal Precht's article on Multi Providers in Angular 2
    Coming back to our ControlValueAccessor story, if a custom component wants to get hold of ControlValueAccessor, it needs to extend the multi-provider for NG_VALUE_ACCESSOR with our own value accessor instance (which is our custom component).What is NG_VALUE_ACCESSOR? ( Short story ) It is a token provided by Angular. Angular internally injects all registered values of ControlValueAccessor on NG_VALUE_ACCESSOR. We just have to extend it. Don't worry, its not as difficult as it sounds. All what we have to do is import NG_VALUE_ACCESSOR and create a provider objects.
  • You don't have to worry about forwardRef and multi:true. You can learn all of this in the articles mentioned above.

Here is a how finally the CustomInput looks after all code changes ( Not including CustomRange because it almost the same, you can still check Plnkr below):

Two quick and interesting things to note:

  1. writeValue() setting the internal _value state. The extra check if( value ){ is to check for initial values. If there is no initial value provided, we are happy with the default values.
  2. On line 39 in registerOnChange(), we save a reference of the internal update function and call it from everywhere we want to update value and inform the containing form about it. In our simple example, we only want to inform the form about value update on every key input. Check line 15 & line 44.

Here is a working Plnkr:

Did you look at the way we provided initial values while creating FormGroup in app component which is also reflected on the view for each component.

Things are looking great here. One last thing that I would like to include here is the way we can access/pull/extract values out of our custom components which are now form enabled as well as ngModel enabled. In the app component template, we also directly defined [(ngModel)] over our custom components and then directly read value from the model. Alternatively, we can access values through form controls collection which is also implemented. The final template looks like:

In this article, I only used reactive/model-driven forms to show if our custom components work well with them but everything will work as is in template driven forms as well. Here is a Plnkr that shows our same custom componets being used inside of both reactive/modal-driven forms and template-driven forms side by side:

Conclusion

Congratulations! You have come a long way. Today we learned how to make our custom Angular Components Form enabled as well as ngModel enabled. If you want to go a little further and learn how to make this code a more DRY, may be a improved version where we can share the creation of providers and not to repeat it in every custom component, you can go to the next article. Thanks for reading!

Feel free to leave a comment, question, suggestion and corrections. Until next time, Happy learning!