Angular: Nested Reactive Forms Using ControlValueAccessors(CVAs)
In this post, we will see how to implement nested reactive forms using composite control value accessors(CVAs). Kara Erickson of the Angular Core Team presented this approach at the Angular Connect 2017 .

In this post, we will see how to implement nested reactive forms using composite control value accessors(CVAs). Kara Erickson of the Angular Core Team presented this approach at the Angular Connect 2017 .
Three Ways to implement nested forms:
We will see the most commonly used techniques to implement nested forms.
- Sub -Form Component Approach (providing
ControlContainer
)
It is built differently for template and reactive driven forms.
Best when you have a really small project and you are using one form module only and you just have to split out a really long form(Excerpt from the talk).
Pro: Quicker to setup and run.
Con: Limited to one form module
2. By passing a handle of the FormGroup
to child components via Input
and referencing it in child templates. There are couple of good tutorials on it.
But the con of using this approach is that you are tightly binding the parent form group with that of child group.
3. Using Composite CVAs.
Pros: Highly Reusable, Portable. Better Encapsulation(Internal Form Controls of the component doesn’t necessarily need to be visible to parent components). This is best used when you have more number of form modules which is typically a large project.
Cons: Need to implement CVA interface results in boilerplate code.
This is what we are going to see now.
What is Control Value Accessor (CVA)?
Here’s what the developers at Google - Angular have to say on this?
The ControlValueAccessor interface is actually what you use to build value accessors for radio buttons, selects, input elements etc in core. It just requires three methods:
writeValue(value: any): void : takes a value and writes it to the form control element (model -> view)
registerOnChange(fn: (value:any) => void): void: takes a function that should be called with the value if the value changes in the form control element itself (view -> model)
registerOnTouched(fn: () => void): takes a function to be called when the form control has been touched (this one you can leave empty if you don’t care about the touched property)
Implementing Composite CVAs
This section assumes that you have a working knowledge on Reactive Forms mainly FormGroups, FormControls, Validations etc.
I have created a sample reactive form component called billing-info-unnested.
import { Component, OnInit } from '@angular/core';
import { FormGroup,FormControl, Validators,AbstractControl, ValidationErrors } from "@angular/forms";
@Component({
selector: 'app-billing-info-unnested',
template: `
<div class="container">
<form [formGroup] ="nestedForm" (ngSubmit) = "onSubmit()">
<div class="row">
<label for="Full Name"> Full Name </label>
<input type="text" formControlName="fname" class="">
</div>
<div class="row">
<label for="Email"> Email </label>
<input type="text" formControlName="email" class="">
</div>
<div class="row">
<label for="addressLine"> Street Address </label>
<input type="text" formControlName="addressLine" class="">
</div>
<div class="row">
<label for="Area"> Area Code </label>
<input type="text" formControlName="areacode" class="">
</div>
<button type="submit" [disabled]="nestedForm.invalid">Place Order</button>
</form>
</div>`,
styleUrls: ['./billing-info-unnested.component.css']
})
export class BillingInfoUnnestedComponent implements OnInit {
public nestedForm: FormGroup = new FormGroup({
fname: new FormControl("", [Validators.required]),
email: new FormControl("", [Validators.required, Validators.email]),
addressLine: new FormControl("", [Validators.required]),
areacode: new FormControl("", [Validators.required, Validators.maxLength(5)])
})
constructor() { }
ngOnInit() {
}
public onSubmit(){
// if(this.nestedForm.invalid){
// return
// }
console.log(" Billing Form", this.nestedForm);
}
}
Output:
nestedForm {
fname:"",
email: "",
addressLine: "",
areacode: ""
}
Now imagine, if our client introduces few more requirements and we need to reuse these controls along with few more form field additions such as.
For checkout form: Shipping types.
For signin/registration: Password, gender, age, etc.
In this scenario, We can reuse by grouping them into components and converting them as form controls. That is, we are going to build our component as a composite control value accessor. We can even provide validations at the component levels. This technique is amazing, trust me. :D
Lets move name
and email
into BasicInfoComponent
, and addressLine
and areacode
into AddressComponent
.
Here we are taking BasicInfoComponent
as example.
<ng-container [formGroup]="basicInfoForm">
<div class="row">
<label for="Full Name"> Full Name </label>
<input type="text" formControlName="fname" class="">
</div>
<div class="row">
<label for="Email"> Email </label>
<input type="text" formControlName="email" class="">
</div>
</ng-container>
The .ts
file looks like this
import { Component, OnInit, forwardRef } from '@angular/core';
import { ControlValueAccessor,FormControl, FormGroup, Validators } from "@angular/forms";
@Component({
selector: 'app-basic-info',
templateUrl: './basic-info.component.html',
styleUrls: ['./basic-info.component.css'],
})
export class BasicInfoComponent implements OnInit {
public basicInfoForm: FormGroup = new FormGroup(
{
fname: new FormControl("",[Validators.required]),
email: new FormControl("", [Validators.required])
});
constructor() { }
ngOnInit() {
}
}
Output:
basicInfoForm: {
fname: "",
email: ""
}
Similarly do the same for AddressComponent
.
Now the parent form component BillingInfo
will look like this
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl} from "@angular/forms";
@Component({
selector: 'app-billing-component',
template:`
<div class="container">
<form [formGroup] ="nestedForm" (ngSubmit) = "onSubmit()">
<app-basic-info formControlName="basicInfo"></app-basic-info>
<app-address-info formControlName = "address"></app-address-info>
<button type="submit" [disabled]="nestedForm.invalid">Place Order</button>
</form>
</div>
`,
styleUrls: ['./billing-info.component.css']
})
export class BillingInfoComponent implements OnInit {
public nestedForm: FormGroup = new FormGroup({
basicInfo: new FormControl(""),
address: new FormControl("")
});
constructor() { }
ngOnInit() {
}
public onSubmit(){
console.log("Billing Info", this.nestedForm.value);
}
}
Now lets try to run this. Here is the Stackblitz demo
Error !
When we try to run the demo, we will encounter error unfortunately.
Error: No value accessor for form control with name: ‘basicInfo’

Lets take a look at the built-in value accessors provided by the Angular Core.

Since our form control (component) doesn’t falls in any these categories, angular compiler throws error stating no value accessor is found.
If you want your custom form control to integrate with Angular forms, it has to implement ControlValueAccessor
Integrate Custom Form Controls into Angular Forms
Lets implement interface ControlValueAccessor
and override all the methods in our BasicInfoComponent
and AddressInfoComponent
. This is what AddressComponent
looks like
import { Component, OnInit } from '@angular/core';
import { ControlValueAccessor,NG_VALUE_ACCESSOR, NG_VALIDATORS, FormGroup,FormControl, Validator, Validators,AbstractControl, ValidationErrors } from "@angular/forms";
@Component({
selector: 'app-address-info',
templateUrl: './address-info.component.html',
styleUrls: ['./address-info.component.css']
})
export class AddressInfoComponent implements OnInit, ControlValueAccessor {
public addressForm: FormGroup = new FormGroup({
addressLine: new FormControl("",[Validators.required]),
areacode: new FormControl('', [Validators.required, Validators.maxLength(5)])
});
constructor() { }
ngOnInit() {
}
public onTouched: () => void = () => {};
writeValue(val: any): void {
val && this.addressForm.setValue(val, { emitEvent: false });
}
registerOnChange(fn: any): void {
console.log("on change");
this.addressForm.valueChanges.subscribe(fn);
}
registerOnTouched(fn: any): void {
console.log("on blur");
this.onTouched = fn;
}
setDisabledState?(isDisabled: boolean): void {
isDisabled ? this.addressForm.disable() : this.addressForm.enable();
}
}
After implementing accessor, we need to tell angular, that for <app-address-info></app-address-info>
form control element, this is its relevant control value accessor.
How can we achieve that?
Lets take a look at how DefaultValueAccessor
is provided in the Angular Forms Package.
export const DEFAULT_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DefaultValueAccessor),
multi: true
};
Lets dissect the syntax above.
- Here the
DefaultValueAccessor
is registered using the built-in tokenNG_VALUE_ACCESSOR.
fowardRef()
denotes refer to references which are not yet defined. Use the instance ofDefaultValueAccessor
which will be later instantiated by AngularuseExisting()
is used to make sure there is only one instance ofDefaultValueAccessor
.- The provider object has a third option,
multi: true
, which is used with DI Tokens to register multiple handlers for the provide event.This is useful if we want to register our custom CVAs toNG_VALUE_ACCESSOR
token
Now we will do the same for our BasicInfoComponent
and AddressInfoComponent
and run the demo again.
StackBlitz demo after implementing CVAs
We see that our error is gone. Hooray!! We have successfully integrated our nested child form control into our Angular Core. But there is still one problem.
Even though our place order button should be disabled if the form is invalid
, we see it is not.

Below is the html code
<button type=”submit” [disabled]=”nestedForm.invalid”>Place Order</button>
So lets Inspect
in developer tools.
Validation Status of Child Components is failing
On Inspect
, we can see that custom form control(child component)<app-basic-info></app-basic-info>
status is valid
, while the form controls inside the basic-info component are still invalid
.

In the previous section, we learnt that
If you want your custom form control to integrate with Angular forms, it has to implement ControlValueAccessor
.
Now, if we want that integration to include validation, we need to implement the Validator
interface as well and provide our custom control as a multi provider to built-in NG_VALIDATOR
token.
Reason:
For Re-validation, the validators will need to be on the top-level form, not at the child component, if you want it to be part of the parent form’s validation.
In our case we need to have validators at BillingInfoComponent
level, so for this purpose we need to convert our component to act as a validator directives such as required
, min
, max
etc.
So lets implement Validator
interface in our child components.
Take a look at how required directive is provided in angular forms package.
export const REQUIRED_VALIDATOR: StaticProvider = {
provide: NG_VALIDATORS,
useExisting: forwardRef(() => RequiredValidator),
multi: true };
We need to do the same for our custom form validator. We need to register a custom form validator using the built-in NG_VALIDATORS
token, and provide multiples instance of our validator provider by using the multi: true
property in the provider object. This tells Angular to add our custom validators to the existing collection.
import { Component, OnInit, forwardRef } from '@angular/core';
import { ControlValueAccessor,FormControl, NG_VALUE_ACCESSOR,NG_VALIDATORS, FormGroup, Validator, AbstractControl, ValidationErrors } from "@angular/forms";
@Component({
selector: 'app-basic-info',
template: `
<ng-container [formGroup]="basicInfoForm">
<div class="row">
<label for="Full Name"> Full Name </label>
<input type="text" formControlName="fname" class="">
</div>
<div class="row">
<label for="Email"> Email </label>
<input type="text" formControlName="email" class="">
</div>
</ng-container>`,
styleUrls: ['./basic-info.component.css'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => BasicInfoComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => BasicInfoComponent),
multi: true
}
]
})
export class BasicInfoComponent implements OnInit, ControlValueAccessor, Validator {
public basicInfoForm: FormGroup = new FormGroup(
{
fname: new FormControl(""),
email: new FormControl("")
});
constructor() { }
ngOnInit() {
}
public onTouched: () => void = () => {};
writeValue(val: any): void {
val && this.basicInfoForm.setValue(val, { emitEvent: false });
}
registerOnChange(fn: any): void {
console.log("on change");
this.basicInfoForm.valueChanges.subscribe(fn);
}
registerOnTouched(fn: any): void {
console.log("on blur");
this.onTouched = fn;
}
setDisabledState?(isDisabled: boolean): void {
isDisabled ? this.basicInfoForm.disable() : this.basicInfoForm.enable();
}
validate(c: AbstractControl): ValidationErrors | null{
console.log("Basic Info validation", c);
return this.basicInfoForm.valid ? null : { invalidForm: {valid: false, message: "basicInfoForm fields are invalid"}};
}
}
So by doing this we get <app-basic-info></app-basic-info>
and <app-address-info></app-basic-info>
re-validated(by calling validate method which in turn validates all the form controls present inside that child component) and status is sent to the parent form component.

Bazinga!! we have accomplished it.
Here is the full stack blitz demo
Thanks for reading! Your feedback is most welcome. If you liked this article, hit that clap button.
Follow me twitter if you want to say “hi” or talk about music.