How to write custom form controls in Angular

we all love two-way data bindings right?. we love when we add “NgModel” directive to some input and values are synced one way another. It’s a really cool feature. but things got’s interesting when we want to add this functionality to our custom component, for example, a component like this

we have two buttons, and when we click “plus” it increases by one when we click “minus” it decreases by one. we want to use this component all over the place, but also we want to use “NgModel” feature on this component.

let’s look at how our component looks like

now let’s try to use this component anywhere and add ngModel to it.

okay we run, and get this error

hmmm, okay a bit confusing error. it says that value accessor ( we don’t know what’s that ) for form control (we don’t know that either ) with unspecified name attribute ( yeah we know name attribute) etc. it says blah blah… name attribute, so we might think that if we add name attribute everything will work. let’s try to add and see what happens

If we run it we will have the next result

okay and now we are lost. “No value accessor for form control with the name: “cash”, even harder, than the previous one.

as we fill find out, there is nothing difficult there, but before I explain how to fix this error, let me show you how angular/forms work under the hood and why it needs this value accessor.

Control Value Accessor

every native HTML input you use inside angular (input,textarea, select, checkbox), it uses directive, that implements Control Value Accessor interface to work with ngModel. it means that this directive is responsible for writing data from the model to view and vice versa, so whenever you use the simplest input like this,

Angular adds a directive to this input behind the scenes. whenever update is coming from the model, ngModel says “hey control value accessor here’s a new value from a model, get it…”, Control Value Accessor updates the view and whenever a user changes something, Control Value Accessor says to ngModel, “hey here’s a new value from user grab it”.

for the sake of simplicity, I will show you how actually angular adds directive, so you guess nothing magic goes there.

That’s Angular source code ( Actually I deleted comments for a shrinking purpose)

let me clear this up, every time angular sees input that is number type and has also ngModel attribute with it, adds this directive to this input. this directive implements ControlValueAccessor interface, that has 4 methods to implement

  • WriteValue
  • RegisterOnChange
  • RegisterOnTouched
  • SetDisabledState

the most important ones are WriteValue, RegisterOnChange

WriteValue

this method is called whenever a value is changed outside, for example, we changed cash amount to 5 from app.component, then this method is called, has a value “5” as a parameter, and this directive updates input value accordingly.

RegisterOnChange

It looks a bit complicated, but It’s as simple as WriteValue. RegisterOnChange function is called once per Initialize and you just have to save the reference of the passed function, so whenever a value is changing from view, you call that function to pass the new value to it, and ngModel will be updated accordingly.

so for now, we know that, to add ngModel to our custom component, we have to implement ControlValueAccessor. the last thing we have to do is to add this component to “NG_VALUE_ACCESSOR” providers array. that’s because whenever ngModel directive is initialized, it uses NG_VALUE_ACCESSOR providers array to fetch current value accessor he is working on (for example NumberValueSelector, SelectValueAccessor etc.), here’s ngModel constructor

so directive is added to NG_VALUE_ACCESSOR like this

Custom Form Control

okay, at final let’s create our custom form control

now whenever we use our component we can just add ngModel directive to it like this

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store