A Simple Example how to implement Nested FormGroups and FormArrays in Angular

Angular&NodeEnthusiast
5 min readJun 24, 2020

--

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 with the DataService.

I have used an array of JSON objects usersList as the data source. There are 3 user objects in the array. fetchUsersList() just returns this array of user objects.

Our final application,which displays the above user data in a table looks like below. Each field in the table, except for the ID column is editable.

Looking at the structure of the object in the usersList array, lets first decide how to break this up into FormGroups and FormArrays.

AppComponent Class

userForm is the root FormGroup 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.

ngOnInit(): void {
this.userForm = this.fb.group({
users: this.fb.array([]),
});
this.userData = this.service.fetchUsersList();
this.displayUsers();
}

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.

In the ngOnInit() lifecycle hook, we are calling the fetchUsersList() of the DataService and storing the array of user objects in the userData property.

To display this data in the form, the displayUsers() is called, where we shall see how to load data into the FormArray users.

displayUsers() {
let transformedUsers = this.userData.map((user: any) =>
this.createUserFormGroup(user)
);
this.userForm.setControl(‘users’, this.fb.array(transformedUsers));
}

First we are mapping each user object from the property userData to a new FormGroup. This mapping is done by the createUserFormGroup() which takes the user object as argument and converts it into a FormGroup with multiple FormControls.

The result of the mapping is a new array of 3 FormGroups corresponding to 3 user objects and this array is stored in a variable transformedUsers.

The value of the transformedUsers variable is assigned to the users FormArray using the setControl().

createUserFormGroup(user: any) {
return this.fb.group({
id: [{ value: user.id, disabled: true }],
name: [user.name, [Validators.required]],
username: [user.username, [Validators.required]],
email: [user.email, [Validators.required, Validators.email]],
address: this.fb.group({
street: [user.address.street, [Validators.required]],
suite: [user.address.suite, [Validators.required]],
city: [user.address.city, [Validators.required]],
zipcode: [user.address.zipcode, [Validators.required]],
geo: this.fb.group({
lat: [user.address.geo.lat, [Validators.required]],
lng: [user.address.geo.lng, [Validators.required]],
}),
}),
phone: [user.phone, [Validators.required]],
website: [user.website, [Validators.required]],
company: this.fb.group({
name: [user.company.name, [Validators.required]],
catchPhrase: [user.company.catchPhrase, [Validators.required]],
bs: [user.company.bs, [Validators.required]],
}),
cars: this.fb.array(this.loadCarsArray(user.cars)),
});
}

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 loadCarsArray(). This method takes the cars property of the user object as argument.

loadCarsArray(cars: any[]) {
let transformedCars = cars.map((car: any) => this.createCarFormGroup(car));
return transformedCars;
}

In this method, we do something very similar. We take each car object of the cars property array and map it to a FormGroup with multiple FormControls. This is done via the createCarFormGroup() which takes the car object as argument.

createCarFormGroup(car: any) {
return this.fb.group({
name: [car.name, [Validators.required]],
models: [car.models, [Validators.required]],
});
}

The result of the mapping is an array of FormGroups corresponding to the array of car objects and this array is stored in a variable transformedCars which is returned back to the caller.

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: 'X1' },
{ name: 'Fiat', models: '100' },
]

We have now constructed the data in the Form. We need to display it in the AppComponent Template now.

We have have defined the FormArray users as below.

<div formArrayName="users">

Next we are iterating through each FormGroup of the FormArray users to form the rows in the table. Each FormGroup is assigned a numerical index which is equal to the iteration index i.

<tr
*ngFor="let x of usersArray.controls; let i = index; trackBy: track"
[formGroupName]="i"
>

What is usersArray? We have defined a getter method in the class to retrieve the the users FormArray.

get usersArray() {
return <FormArray>this.userForm.get(‘users’);
}

Now lets see how we have written out the nested FormGroups :address and geo.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 cars 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>
<label>
<b>Car Name:</b>
<input type="text" formControlName="name" />
<b>Model:</b>
<input type="text" formControlName="models" />
</label>
</td>
</ng-container>
</ng-container>

I have also displayed the Form’s value in the template below the table. You can edit the table fields and immediately notice a change in the Form’s value.

You can find the entire working example below:

--

--

Angular&NodeEnthusiast
Angular&NodeEnthusiast

Written by Angular&NodeEnthusiast

Loves Angular and Node. I wish to target issues that front end developers struggle with the most.