Angular ControlValueAccessor by example
Posted on August 8, 2017

The ControlValueAccessor is Angulars interface to interact with a form control.
At sLAB we currently develop an angular client for the sMOTIVE product. We wanted to extend some form controls with own behavior and had some issues with the ControlValueAccessor.
I did some investigations with plunker to address those issues.
- Outer component containing the form.
- Shows the properties of the form and the ‘business model’ (value).
- The form control implementing the ControlValueAccessor interface.
- These buttons trigger changes inside the control.
- The buttons trigger changes from the outer component.
my-input.component.ts
|
|
The above code is an excerpt of the form control.
There are four important methods:
writeValue(obj: any)
through this method angular sets value with binding. This method is part of the ControlValueAccessor interface.changeMe()
changes the inner value and propagate the changed value through the registered change function.propagateInitial()
does not touch the inner value and calls the registered function with the value ‘initial’. With this method I wanted to test if angular remembers the initial value of a control and reset the dirty/pristine flag.propagateSomethingValue()
does not touch the inner value and calls the registered function with a complete different value.
Independently of the form type the following facts can be noticed:
writeValue()
has no effect on the dirty flag of the form.changeMe()
changes the inner value, marks the form as dirty and sets the value on the outer component.propagateIntial()
marks the form as dirty and sets the value of the outer component to the value ‘initial’.propagateSomethingValue()
marks the form as dirty sets the outer value.
With this in mind, we can note some rules:
- Do not call the registered change function inside the writeValue otherwise the form is always dirty.
- Change the inner value and propagate the change with the registered function. Hold the inner and the outer value in sync!
- User driven (button click, key press) actions should result in value propagation through the registered function.
template-driven forms
|
|
A template-driven form binds the value of the outer component with [(ngModel)] to the control.
|
|
changeThat()
sets the value on the outer component and the binding sets the value on the control.resetForm()
resets the formu. This changes dirty to false, pristine to true, but the control will be called with writeValue(null)! So this changes the controls value to null independently of any initial value. After calling the reset the developer is responsible to set the initial value again from the outer component.
reactive forms
|
|
A template-driven form binds the value of the outer component with [(ngModel)] to the control.
|
|
constructor()
creates the form with angular form classes. I didn’t use the builder to show the relation between the form classes and the parts of the form.ngOnInit()
sets a change listener on the FormGroup. This listener is responsible to change the business model. With reactive forms this is not done automatically through binding!changeThat()
changes the form withsetValue()
. This can also be done withpatchValue()
.resetForm()
resets the form. The method accepts a JSON object, which is used to initially set the form values.markPristine()
just resets the dirty/pristine property of a single control. All other properties of the control stay the same.