Keywords: Angular | Custom Form Controls | ControlValueAccessor | NG_VALUE_ACCESSOR | Reactive Forms
Abstract: This article provides an in-depth analysis of the 'No value accessor for form control' error in Angular 4 and its solutions. By implementing the ControlValueAccessor interface and registering the NG_VALUE_ACCESSOR provider, developers can create custom form controls that integrate seamlessly with Angular's reactive and template-driven forms. The article includes step-by-step code examples, explaining how to transform custom elements like divs into fully functional form controls, and covers core concepts such as the writeValue, registerOnChange, and registerOnTouched methods.
Problem Background
In Angular development, when attempting to use formControlName on a custom element, you might encounter the error message: ERROR Error: No value accessor for form control with name: 'surveyType'. This typically occurs when the element is not a standard form control, such as input or select. For instance, a user may have a custom div element and want to bind a value to a form control via a click event, but directly applying formControlName results in this error.
Error Cause Analysis
Angular's form system relies on the ControlValueAccessor interface to manage data flow between form controls and the model. Only directives that implement this interface can use formControlName. Standard HTML elements like input have built-in implementations, but custom elements (e.g., div) do not, requiring manual implementation. The error message "No value accessor" indicates that Angular cannot find a corresponding value accessor to handle the form control.
Solution: Implementing the ControlValueAccessor Interface
To resolve this issue, create a custom component and implement the ControlValueAccessor interface. This interface requires defining three core methods:
writeValue: Used to write the model value into the view. For example, when the form control's value changes, this method updates the display of the custom element.registerOnChange: Registers a callback function that is called when the view value changes, notifying Angular to update the model.registerOnTouched: Registers a callback function that is called when the component is touched (e.g., gains focus), used for handling validation states.
Here is an example code snippet demonstrating how to implement these methods:
import { Component, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
selector: 'app-custom-control',
template: `
<div (click)="onClick()" [class.selected]="isSelected">
<md-icon>{{ icon }}</md-icon>
<span>{{ description }}</span>
</div>
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomControlComponent),
multi: true
}
]
})
export class CustomControlComponent implements ControlValueAccessor {
value: any;
isSelected = false;
onChange = (value: any) => {};
onTouched = () => {};
writeValue(value: any): void {
this.value = value;
this.isSelected = value === this.value; // Example logic to update view based on value
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
onClick(): void {
this.onChange(this.value); // Trigger value change
this.onTouched(); // Mark as touched
}
}In this code, we define a custom component CustomControlComponent that implements the ControlValueAccessor interface. When the user clicks the div, the onClick method calls onChange and onTouched, enabling interaction with Angular's form system.
Registering the Provider
After implementing the interface, you must register the NG_VALUE_ACCESSOR provider to inform Angular that the component is a value accessor. Using forwardRef handles circular dependencies, and multi: true allows for multiple value accessors. This is essential in Angular's dependency injection system to ensure the component is correctly recognized.
Using the Custom Control
Once implemented, the custom control can be used like a standard control. In reactive forms, apply formControlName directly:
<form [formGroup]="myForm">
<app-custom-control formControlName="surveyType"></app-custom-control>
</form>In template-driven forms, use ngModel:
<app-custom-control [(ngModel)]="surveyType"></app-custom-control>This way, clicking the custom element updates the form control's value, achieving binding with the model.
Additional Notes and Best Practices
According to reference articles, the "No value accessor" error can stem from two common scenarios: using a third-party control that does not register NG_VALUE_ACCESSOR, or forgetting to register the provider for a custom control. In such cases, it is recommended to implement ControlValueAccessor rather than relying on ngDefaultControl, which is only suitable for specific situations.
In practice, ensure that the custom control's logic aligns with Angular's form lifecycle. For example, handle initial values in writeValue, and call onChange and onTouched in events to maintain responsiveness. Additionally, test the control's behavior in different form types to ensure compatibility.
Conclusion
By implementing the ControlValueAccessor interface and registering the NG_VALUE_ACCESSOR provider, developers can extend Angular's form capabilities to create flexible custom controls. This not only resolves the "No value accessor" error but also enhances code reusability and maintainability. With the examples and explanations in this article, readers should be able to apply this technique effectively in real-world projects.