A Simple Example how to implement Nested FormGroups and FormArrays in Angular
Accessing and displaying Nested FormGroups and FormArrays can be quite confusing for a beginner. If you are new to FormArrays, please do check the below story for an an introduction.
Lets begin.
I have used an array of JSON objects as the data source. There are 10 objects in the array. The array userData is exported from users.ts as show below.

Looking at the structure of the object, lets first decide how to break this up into FormGroups and FormArrays. I have created a Component which uses this data to display in a table.
export class NestedComponent implements OnInit {
constructor() { }
userForm:any;ngOnInit(): void {
this.userForm=new FormGroup({
users:new FormArray([])
})
this.loadData();
}loadData(){
for(let x in userData){
this.userForm.get('users').push(new FormGroup(
{
id: new FormControl({value:userData[x].id,disabled:true}),
name: new FormControl(userData[x].name,[Validators.required]),
username: new FormControl(userData[x].username,[Validators.required]),
email: new FormControl(userData[x].email,[Validators.required,Validators.email]),
address:new FormGroup({
street: new FormControl(userData[x].address.street,[Validators.required]),
suite: new FormControl(userData[x].address.suite,[Validators.required]),
city: new FormControl(userData[x].address.city,[Validators.required]),
zipcode: new FormControl(userData[x].address.zipcode,[Validators.required]),
geo:new FormGroup({
lat:new FormControl(userData[x].address.geo.lat,[Validators.required]),
lng:new FormControl(userData[x].address.geo.lng,[Validators.required])
})
}),
phone: new FormControl(userData[x].phone,[Validators.required]),
website: new FormControl(userData[x].website,[Validators.required]),
company:new FormGroup({
name:new FormControl(userData[x].company.name,[Validators.required]),
catchPhrase:new FormControl(userData[x].company.catchPhrase,[Validators.required]),
bs:new FormControl(userData[x].company.bs,[Validators.required])
}),
cars:new FormArray(this.loadCars(x))
}
))}}loadCars(x):any{
let controls=[];
for(let y in userData[x].cars){
controls.push(new FormGroup({
name:new FormControl(userData[x].cars[y].name,[Validators.required]),
models:new FormControl(userData[x].cars[y].models,[Validators.required])
}))
}
return controls;
}track(item:any,index:number){
return index;
}submit(f)
{
console.log(f.value);
}
}
userForm is the main form containing the table where I want to display the user data.
userForm is a FormGroup containing a FormArray named users. The FormArray is initialised to [] as below.
this.userForm=new FormGroup({
users:new FormArray([])
})
1.We can compare the FormArray users to the userData array from users.ts.
2.Each object in the userData can be compared to the FormGroup in the FormArray.
3.An object nested inside an object can be compared to a FormGroup nested inside another FormGroup.
4. An array nested inside an object can be compared to a FormArray (consisting of multiple FormGroups) nested in a FormGroup.
loadData() is called once the component loads to push the userData into the FormArray users.
We are iterating through the userData and for each iteration, a FormGroup is pushed into the FormArray as you can see below.
loadData(){
for(let x in userData){
this.userForm.get('users').push(new FormGroup(
{
id: new FormControl({value:userData[x].id,disabled:true}),
name: new FormControl(userData[x].name,[Validators.required]),
username: new FormControl(userData[x].username,[Validators.required]),
email: new FormControl(userData[x].email,[Validators.required,Validators.email]),
address:new FormGroup({
street: new FormControl(userData[x].address.street,[Validators.required]),
suite: new FormControl(userData[x].address.suite,[Validators.required]),
city: new FormControl(userData[x].address.city,[Validators.required]),
zipcode: new FormControl(userData[x].address.zipcode,[Validators.required]),
geo:new FormGroup({
lat:new FormControl(userData[x].address.geo.lat,[Validators.required]),
lng:new FormControl(userData[x].address.geo.lng,[Validators.required])
})
}),
phone: new FormControl(userData[x].phone,[Validators.required]),
website: new FormControl(userData[x].website,[Validators.required]),
company:new FormGroup({
name:new FormControl(userData[x].company.name,[Validators.required]),
catchPhrase:new FormControl(userData[x].company.catchPhrase,[Validators.required]),
bs:new FormControl(userData[x].company.bs,[Validators.required])
}),
cars:new FormArray(this.loadCars(x))
}
))}}
id,name,username and email are simple FormControls.
address has to be a FormGroup because address is an object itself with multiple properties. These properties are FormControls.
geo is a FormGroup nested inside the address FormGroup because geo is an object nested inside the address object.
phone and website again are simple FormControls.
company is a FormGroup since it is an object itself with mutliple properties.
cars is a FormArray which is initialised using a function loadCars(). As you can see,we are passing the iteration index as well to the method to map the user to the car.
loadCars(x):any{
let controls=[];
for(let y in userData[x].cars){
controls.push(new FormGroup({
name:new FormControl(userData[x].cars[y].name,[Validators.required]),
models:new FormControl(userData[x].cars[y].models,[Validators.required])
}))
}
return controls;
}
In loadCars(), we are iterating through the cars array in userData, corresponding to the user at index x in userData. This should explain why we are passing the iteration index from loadData() to loadCars().
As you can see below, each object in the cars array below can be compared to a FormGroup and each property inside the object can be compared to a FormControl.
“cars”: [{ “name”:”Ford”, “models”:”Fiesta” },{ “name”:”BMW”, “models”:”X3" },{ “name”:”Fiat”, “models”:”500" }]
We have initialised a block variable controls to an empty array. For each iteration of cars,we are pushing a FormGroup into controls. Each FormGroup contains 2 FormControls: name and models.
We have now constructed the data in the Form. We need to display it in the template now.
<form [formGroup]=”userForm” (ngSubmit)=”submit(userForm)”>
<div formArrayName=”users”>
<table class=”table-bordered”>
<tr><th>ID</th>
<th>NAME</th>
<th>USERNAME</th>
<th>EMAIL</th>
<th>STREET</th>
<th>SUITE</th>
<th>CITY</th>
<th>ZIPCODE</th>
<th>LAT</th>
<th>LNG</th>
<th>PHONE</th>
<th>WEBSITE</th>
<th>COMPANY NAME</th>
<th>COMPANY CATCHPHRASE</th>
<th>COMPANY BS</th>
<th colspan=”3">CARS OWNED</th></tr><tr *ngFor=”let x of this.userForm.get(‘users’).controls;let i=index;trackBy:track” [formGroupName]=”i”><td><input type=”text” formControlName=”id”></td>
<td><input type=”text” formControlName=”name”></td>
<td><input type=”text” formControlName=”username”></td>
<td><input type=”text” formControlName=”email”></td><ng-container formGroupName=”address”>
<td><input type=”text” formControlName=”street”></td>
<td><input type=”text” formControlName=”suite”></td>
<td><input type=”text” formControlName=”city”></td>
<td> <input type=”text” formControlName=”zipcode”></td><ng-container formGroupName=”geo”>
<td><input type=”text” formControlName=”lat”></td>
<td><input type=”text” formControlName=”lng”></td>
</ng-container>
</ng-container><td><input type=”text” formControlName=”phone”></td>
<td><input type=”text” formControlName=”website”></td><ng-container formGroupName=”company”>
<td><input type=”text” formControlName=”name”></td>
<td><input type=”text” formControlName=”catchPhrase”></td>
<td><input type=”text” formControlName=”bs”></td>
</ng-container><ng-container formArrayName=”cars”>
<ng-container *ngFor=”let y of x.get(‘cars’).controls;let j=index;trackBy:track” [formGroupName]=”j”><td>
<input type=”text” formControlName=”name”><input type=”text” formControlName=”models”>
</td>
</ng-container>
</ng-container>
</tr>
</table>
</div><button type=”submit”>Submit</button>
</form>
We have have defined the FormArray users as below.
<div formArrayName=”users”>
Next we are iterating through each FormGroup of the FormArray to form the rows in the table. Each FormGroup is assigned a numerical index which is equal to the iteration index.
<tr *ngFor=”let x of this.userForm.get(‘users’).controls;let i=index;trackBy:track” [formGroupName]=”i”>
Now lets see how we have written out the nested FormGroups :address and group.There is a< ng-container> nested inside another <ng-container>
<ng-container formGroupName=”address”>
<td><input type=”text” formControlName=”street”></td>
<td><input type=”text” formControlName=”suite”></td>
<td><input type=”text” formControlName=”city”></td>
<td> <input type=”text” formControlName=”zipcode”></td><ng-container formGroupName=”geo”>
<td><input type=”text” formControlName=”lat”></td>
<td><input type=”text” formControlName=”lng”></td>
</ng-container>
</ng-container>
This is how the nested FormArray is written out. As you can see,we are iterating through the FormArray and each FormGroup inside the FormArray is assigned a numerical index j.
<ng-container formArrayName=”cars”>
<ng-container *ngFor=”let y of x.get(‘cars’).controls;let j=index;trackBy:track” [formGroupName]=”j”><td>
<input type=”text” formControlName=”name”><input type=”text” formControlName=”models”>
</td>
</ng-container>
</ng-container>



Please let me know your comments:)