If you want your components to work with declarative (template-driven) and imperative forms, you have to implement the interface ControlValueAccessor
. The methods defined by this interface permit Angular 2 to read and set the state of the control in question.
Such controls can be used with ngModel
or ngControl
. To illustrate this, the following example uses a custom date-control
that binds a variable date
with ngModel
:
<!-- Deklaratives (template-driven) Forms-Handling
<date-control [(ngModel)]="date"></date-control>
Alternatively, such a component can use imperative forms to bind to a predefined Control
-object. The following example illustrates this by binding the date-control
with ngControl
to the Control
with the name date
. Angular expects this Control-object in the ControlGroup
that has been specified with ngFormModel
:
<!-- Imperatives Forms-Handling -->
<form [ngFormModel]="filter">
<date-control ngControl="date"></date-control>
[...]
</form>
The ControlGroup
and the Control
are provided via the component:
@Component({
selector: flight-search,
template: require(./flight-search.component.html),
directives: [DateControlComponent]
})
export class FlightSearchImpComponent {
public filter: ControlGroup;
constructor(private fb: FormBuilder) {
this.filter = fb.group({
date: [2016-05-01]
});
}
[...]
}
This article describes the steps necessary to implement ControlValueAccessor
. The total sample can be found here.
ControlValueAccessor
The interface ControlValueAccessor
provides three methods for synchronizing the state of a control with the object graph Angular uses to represent a form:
//
// From the Angular2-Sources
//
export interface ControlValueAccessor {
writeValue(obj: any): void;
registerOnChange(fn: any): void;
registerOnTouched(fn: any): void;
}
To write a value to the control, Angular uses the method writeValue
. Since Angular must know about changes to the date, the SPA-flagship uses registerOnChange
and registerOnTouched
to register callbacks. The control has to call the first one, when the users changes the state. The latter one indicates that the field at least had the focus.
Implementing ControlValueAccessor
The following example demonstrates the implementation of the interface ControlValueAccessor
. For this, it shows a simple component for editing dates. The method splitDate
takes a date and breaks it down into its parts, which are offered for editing by the (here not shown) template. The method apply
is putting those together to a date again.
import { Component } from @angular/core;
import { ControlValueAccessor, NgControl } from @angular/common;
@Component({
selector: date-control,
template: require(./date-control.component.html)
})
export class DateControlComponent
implements ControlValueAccessor {
day: number;
month: number;
year: number;
hour: number;
minute: number;
constructor(private c: NgControl) {
c.valueAccessor = this;
}
writeValue(value: any) {
this.splitDate(value);
}
onChange = (_) => {};
onTouched = () => {};
registerOnChange(fn): void { this.onChange = fn; }
registerOnTouched(fn): void { this.onTouched = fn; }
splitDate(dateString) {
var date = new Date(dateString);
this.day = date.getDate();
this.month = date.getMonth() + 1;
this.year = date.getFullYear();
this.hour = date.getHours();
this.minute = date.getMinutes();
}
apply() {
var date = new Date();
date.setDate(this.day);
date.setMonth(this.month - 1);
date.setFullYear(this.year);
date.setHours(this.hour);
date.setMinutes(this.minute);
date.setSeconds(0);
date.setMilliseconds(0);
this.onChange(date.toISOString());
this.onTouched();
}
}
To be able to interact with the Forms-Handling of Angular 2, the component implements the interface ControlValueAccessor
. In addition, it gets the current NgControl
by the means of Dependency Injection. Angular uses this instance to represent the control within the object graph that has been created for the form. It sets the property ValueAccessor
to the component itself. This means, that the component is its own ValueAccessor
.
The implementation of writeValue
takes a new value from the framework and delegates it to splitDate
. The implementations of registerOnChange
and registerOnTouched
however take callbacks from Angular and puts them into onChange
and onTouched
.
After the date has been changed, the template invokes the method apply
. It adds the individual parts of the date together and passes it to Angular using onChange
. In addition, for the sake of completeness, it executes the calback onTouched
.