Have you ever got the following error while assigning a FormControl to some element in your component’s HTML?

ERROR Error: No value accessor for form control with unspecified name attribute

Custom FormControl error

If yes, this blog will help you to understand what that error is and how to resolve it. I used bootstrap dropdown as an example but the concept remains the same for all custom FormControl. Read the full article to use it on any customized FormControl.


Let’s first understand why Angular throws the error. Consider the following example of a simple input textbox.

// .ts
inputFormControl = new FormControl('hello world', Validators.required);

// .html
<input type="text" [formControl]="inputFormControl">

The above code simply adds one input textbox and sets its value as “hello world”. All basic form elements like <input><textarea><select> have its APIs to set value, to get value, to add validators like maxlengthminlengh, etc.

Angular implements these APIs behind the scene and gives us FormControl class to perform form related operations. In this case, Angular knows how to set the value of input textbox, how to validate if the input textbox has any value or not.

But in case of bootstrap dropdown or any other custom form element, Angular doesn’t know this stuff. A bootstrap dropdown is not a basic form element. It does not have its API to set or get the values. Instead, its styles are set in such a way that it works like a dropdown ( <select> HTML tag ). Therefore, Angular throws the error.

Let’s see how to resolve this.


This blog uses ngx-bootstrap ( an Angular wrapper of bootstrap ) dropdown so that we don’t have to use jQuery.

1. Add ngx-bootstrap dropdown:

ng add ngx-bootstrap  --component dropdowns

2. Create a new component:

ng generate component custom-dropdown

3. Add ngx-bootstrap dropdown to custom-dropdown:

custom-dropdown.component.ts

Line-9: Define a property options to hold the dropdown items.

Line-11: Define a property selectedOption to hold the value of dropdown ( selected option ).

Line-16: Initialize the options with an array of simple items.

Line-21: Initialize the selectedOption with the first item of options.

custom-dropdown.component.html

dropdowndropdownToggle*dropdownMenu are simply directives of ngx-bootstrap.

Line-5: Show selectedOption as the selected item.

Line-10: Iterate through all options to show them as items of a dropdown.

4. Create custom FormControl:

To use custom-dropdownas FormControl, it needs to implement the interface ControlValueAccessor. This interface has three methods ( writeValueregisterOnChangeregisterOnTouched ) which needs to be overridden by our CustomDropdownComponent class.

custom-dropdown.component.ts

Line-2: Implement interface ControlValueAccessor

Line-18, 20, 22: Define all three methods of ControlValueAccessor as blank methods.

5. Assign a FormControl to custom-dropdown:

app.component.ts

Line-11: Initialize dropdownControl as new FormControl.

app.component.html

As soon as you save both files, Angular throws an error in the console. We will resolve that now.

6. Override writeValue() method:

writeValue() method is called every time we set the value of a FormControl. It is the passing of value from .ts to .html file.

custom-dropdown.component.ts

Line-18: Whatever value we set by dropdownControl.setValue('value') is going to be passed in writeValue() method as an argument. We use that argument to assign it to selectedOption variable.

7. Override registerOnChange() method:

What is the way to get FormControl value every time it gets changed? As you know, it is

formControl.valueChange.subscribe((value) => { 
   // some operations with value
})

How can we do that with our custom form-control? In our case of a dropdown, we have to trigger the change in the value of FormControl when one clicks on an option.

As writeValue()method passes the value from .ts to .html, registerOnChange() method passes the value from .html to .ts. Let’s see how.

custom-dropdown.component.html
custom-dropdown.component.ts

Line-7: Declaration of the function named onChange() .

Line-24: registerOnChange() method takes one argument which is a function. Whenever we want to notify that value is changed in FormControl we have to call this function. We assign the reference of this function to onChange function. Now, we have to call onChange function whenever we want to notify the change in the value of FormControl.

Line-28: changeSelectedOption() method is called when one clicks on an option in the dropdown as we bind that method on click event of an option. In this method, we assign selectedOption as option argument, which is the clicked option. We call onChange() function with option as argument.

8. Override registerOnTouched() method:

This method works the same way registerOnChange() works. We registered a function onChange() with the function provided in the argument of registerOnChange() . Every time we change the value of dropdown we have to call onChange() function. In the same way, we can declare one more function called onTouched() which is registered with the function provided in the argument of registerOnTouched() . Now, onTouched() needs to be called every time form-control gets the touch event.

9. Register component to NG_VALUE_ACCESSOR:

NG_VALUE_ACCESSOR is a provider that specifies a class that implements ControlValueAccessor interface. It is used by form directives to inject the value accessors.

custom-dropdown.component.ts

Line-8: Pass the provider object to the providers array of the component.

And that’s it. As soon as you save the file, you see the error is gone and our dropdown works as FormControl.

Resources:

  1. ngx-bootstrap dropdown
  2. Complete Code