NOTE: Please note that this article refers to an old version of Angular. Core concepts and ideas may still be useful but not all examples will work with current Angular without changes.
So you are starting to flex your new Angular 2 muscles and have built the mother of all custom form controls. I mean that thing can do everything except make coffee… And then the moment comes to extract and use it as a separate component in your application.
You plug it into your HTML form with ngModel and a name, thinking your work is done and as your browser refreshes you start seeing all sorts of errors appearing on the console.
As it turns out, your journey is far from over and today we will take a look at what Angular2 expects from you if you want to build components that can talk to ngModel.
TL;DR: Here’s the code
NOTE: As of RC5 of Angular 2.0 you need to import either the Template Driven Forms or Reactive Forms modules in order to use ngModel in your templates. You can see how to do this later in this article.
For the example we will use a basic input component. You may argue that this component could easily by replaced by simply using the native HTML input element, and you would be right!
But we are trying to clearly show how to expose the component to the required interfaces and, to avoid making the example overly complex, we will be sticking to this example. Once you understand how this works you can easily build on it to make more complex controls as well. We will also not be looking at a11y in this post, also for simplicity. But be aware that there is a lot of hidden work to be done if you want your controls to work well with assistive technologies.
So without further ado, here is our component:
We are then able to use this custom control as follows:
And here it is in action:
What did I just see, man? My eyes are bleeding!
Don’t panic! We will have a brief look at what just happened. But first it is perhaps important to state the following:
- By using native HTML form elements in your code, you are avoiding having to write this code. Always consider using them first.
- For the cases where you do need more power and want to create your own custom form control, Angular 2 gives you all you need to make it work!
So let us dive into the code…
Bootstrap: Getting things started
As we will use ngModel and other goodies from the Angular 2 forms goodiebag, we need to import either of the two forms modules provided by Angular 2. In this article we will use Template Driven Forms
First we create our own Application Module:
Note that we import BrowserModule as this is required by all browser applications and we import FormsModule as this contains the forms goodies we need, such as ngModel.
We declare our AppComponent and set it as the Bootstrap Component for our application and we also declare our CustomInputComponent here so we can use it everywhere in our application. We assume here that this component has already been created, but you can always add the declaration later if needed.
Once we have our correctly configured our Application Module we can bootstrap our Angular application:
If this seems like seriously dark magic to you, please head to the official Angular documentation and read about Angular Modules.
NG_VALUE_ACCESSOR and the multi-provider: The glue
As you know, we can register multi-providers in Angular 2. In short, it is possible to extend existing providers to avoid duplicate providers from clashing. And for this communication to work we need to tell the NG_VALUE_ACCESSOR token inside Angular 2 that our class exists and has got something to say to the data bindings.
That we do with:
Here we are telling this token about our component class. Important is the use of forwardRef. This is needed as our class will not be defined yet when the provider is registered and we need to tell the Provider constructor about it so it can wait for the class to be defined.
And once we have created this provider we need to tell our component to use it:
Now Angular 2 knows about our form control component class, but we are not done yet.
Implementing ControlValueAccessor: The machinery
We also need to do the necessary work to manage the data and events inside our new component. And for this we need to implement the ControlValueAccessor interface that Angular 2 gives us.
Let us implement this interface and hook it into our data model.
Let us first focus on the last three functions. These are functions we have to implement from the ControlValueAccessor interface and these are the functions we need to use internally to communicate with the outside world.
The writeValue function allows you to update your internal model with incoming values, for example if you use ngModel to bind your control to data.
The registerOnChange accepts a callback function which you can call when changes happen so that you can notify the outside world that the data model has changed. Note that you call it with the changed data model value.
The registerOnTouched function accepts a callback function which you can call when you want to set your control to touched. This is then managed by Angular 2 by adding the correct touched state and classes to the actual element tag in the DOM.
NOTE: Both these functions are later provided by Angular 2 itself. But we need to register dummy functions to be able able code and transpile it without errors.
The rest of the code are all internal component stuff! Providing function placeholders for the callbacks before they are registered, a getter and setter for the data and a function to capture the blur event of the internal input to mark the entire component as touched. No rocket science there.
And there you have it! Now you can fully integrate your shiny new control with directives like ngControl and ngModel!
Go forth and conquer!
If you want to know more about how to build extremely powerful form controls in Angular 2, the Angular Material 2 repository is a veritable treasure trove of information on the subject. If you are willing to dive into the code.