Create a custom form control using Angular's ControlValueAccessor

Share this video with your friends

Social Share Links

Send Tweet
Published 6 years ago
Updated 5 years ago

If you're coming from AngularJS (v1.x) you probably remember the ng-true-value and ng-false-value directive which allowed to map custom boolean values like "yes" or "no" or whatever other value you had, onto your HTML form. In this lesson we're going to implement our own trueFalseValue directive for Angular, by directly hooking into Angular's form API. A nice occasion to learn about the ControlValueAccessor interface.

Instructor: [00:00] Here, we have a very simple React form inside an Angular component. We have bound it to the form variable, which we defined here, and which we initialize in the onInit. We have one property, which we initialize with root, and directly bind it to that checkbox.

[00:16] As you can see, when we click here, we get here the form value printed out, and we see how the value binds back to our model. Now, often Boolean values are not actually always true and false, but they might have the form of yes, no, or something else.

[00:30] Now, in Angular one, if you're coming from Angular one, actually, there was a form of defining such true and falsey values. You could specify something like ngTrue value, and you could it provide it an alternative value instead of true and false.

[00:46] Let's try and implement something similar here, and at the same time, learn about custom form value accessors. We want to have something like true value was equal to yes, and then a false value which is equal to no, let's say.

[01:00] Now, the next step is to write a directive which picks up these values here. We could either write a directive that targets all checkboxes, or as I prefer here in this example, create a specific one which is called true-false value directive.

[01:15] Let's create such a directive. The scaffold of that would look something like that. We have here our true-false value directive, and let's also match only the type, checkbox. Let's also get rid of these linter error messages here for a second.

[01:35] What we want to do is to actually hook directly into the forms API, so that we get the values from the models, and we can then bind back the values from the view to the model again. We can do that by implementing that control value accessor, which comes from the Angular forms.

[01:51] We need to implement here a couple of functions. First of all, we won't need those here, the ngRegister on touched, and disable state. We will go and first implement that right value. What the right value does is, it actually implements the changes which come from the model and go to the view.

[02:11] What we have to do is actually to take that value, and bind it onto our checkbox in this case. Before, we need to get our true and false values, which we have to find here. That will be simple input controls into our directive.

[02:26] I have here true value and an input for the false value, of course. Let's also initialize them with the according defaults. We also need to get that input imported. Now, in our right value here, we can do something like if object is equals to this.true value, then we need to write it onto our element, and we need to get hold of our checkbox.

[02:50] What we have to do is, in the constructor, we can get it via the element ref. That gets imported from the Angular core up there. Then we can say something like this.elementRef.nativeElement.check equal true. In the other case, we simply set it to false. We again repeat this here, and set it to false.

[03:16] Now, accessing the native element is actually not a good approach, because whenever that runs in a non-DOM environment, such as in web worker or on the server side, that would actually break. There's a better approach by using the renderer. We can import that again. We use the renderer, too.

[03:34] We change this line here by saying this.renderer.setProperty of this.elementRef.nativeElement. We give it that property, which is checked in that case, and we give it the value. Similarly, we do it also for our false part here.

[03:52] With that, we have implemented our change that writes the model value onto our view. The next thing is to write the value from our view back to our model. That's what you do in the registerOnChange. Here, we get basically a function that gets passed in by the forms API, which can basically remember here in our class, and use it later whenever a change happens.

[04:12] What we do is, here at the top, we define an empty function, so the propagateChange. This is simply a function which takes some value, which will be any. The default implementation here is simply empty.

[04:24] Then further down here, in the registerOnChange, we simply remember that function, register it to our propagate change function above. Now, whenever someone clicks our checkbox, we need to actually register on those changes, and then invoke that propagateChange function.

[04:40] We can register on the checkbox our directive is placed on by the host listener. In the host listener, we register for the change. We also want to get the event that gets basically passed in. Let's define that onHostChange function here.

[04:54] Whenever the change happens now, that host listener will register and invoke our function, and pass in that event object. What we can do now is to simply invoke our propagateChange function. We use that event target value, and check for that checked property.

[05:13] Whenever that is true, we want to broadcast a true value, which has been configured in our directive. Otherwise, we broadcast the false value. There's one last piece missing here, which is to register our custom control value accessor here.

[05:28] We can do that by registering here a new provider. We have to basically use that ngValue accessor token, which comes from the Angular forms. We use an existing value, which will be here our custom directive class.

[05:44] By using the forward ref, we basically tell the Angular compiler that this class comes after that metadata that has been defined here. This is a workaround you have to implement for now. We also need to specify that this is a multi provider. Basically, more ngValue access could obviously be defined in our application.

[06:04] Let's save this. Now, we can go to our app module, and register our directive. If we then go back to our app component, we have already bound our directive on top of here. We have provided already our new truthy and falsey values.

[06:18] Now, if we click, you can see how the value that got bound back to our model is no in this case, and yes in the other case.

~ 3 years ago

This is a bit outdated now.

Markdown supported.
Become a member to join the discussionEnroll Today