In this guide, we will show you how to build a multi-level nested FormArray Example. The only way to build nested forms in angular is by using the FormArray
. We show you how to add form fields dynamically in a 2 level nested Form. Our Form will consist of an employee and his skills. The user will be able to add/remove employee’s and under each employee, you can add/remove any number of skills. If you have not used FormArray, then we suggest you read the FormArray Example in Angular.
Table of Contents
Angular Nested Formarray
Create a new Angular Project. Open the app.module.ts
and import the ReactiveFormsModule
1 2 3 | import { ReactiveFormsModule } from '@angular/forms'; |
Also, add it to the imports array of NgModule
of the AppModule
1 2 3 4 5 6 7 8 | @NgModule({ imports: [BrowserModule, ReactiveFormsModule], declarations: [AppComponent, HelloComponent], bootstrap: [AppComponent] }) export class AppModule {} |
You can refer to the completed Source Code from StackBlitz
Import FormArray
Go to the AppComponent
. Import the FormArray from the Angular Forms Module.
1 2 3 | import { FormGroup, FormArray, FormBuilder } from '@angular/forms' |
Build a Form Model
The First task is to build a Form Model empForm
. It has only one property a FormArray of employees.
1 2 3 4 5 6 7 8 9 | empForm:FormGroup; constructor(private fb:FormBuilder) { this.empForm=this.fb.group({ employees: this.fb.array([]) , }) } |
Employee FormArray
Helper method, which returns the employees
FormArray from the model empForm
1 2 3 4 5 | employees(): FormArray { return this.empForm.get("employees") as FormArray } |
The newEmployee
method creates a new employee FormGroup and returns it. It has three properties. firstName
, lastName
and skills
FormArray.
1 2 3 4 5 6 7 8 9 | newEmployee(): FormGroup { return this.fb.group({ firstName: '', lastName: '', skills:this.fb.array([]) }) } |
Next, the method to add an employee. It uses the newEmployee
method which returns the Employee
FormGroup and ads it to employees
array.
1 2 3 4 5 | addEmployee() { this.employees().push(this.newEmployee()); } |
Method to remove the employee form the array. It needs the index position to remove it.
1 2 3 4 5 | removeEmployee(empIndex:number) { this.employees().removeAt(empIndex); } |
Skills FormArray
Under each employee, we have skills array. Hence create helper method which returns a skills array from the employee array. We need to pass the index position of the employee array as argument.
1 2 3 4 5 | employeeSkills(empIndex:number) : FormArray { return this.employees().at(empIndex).get("skills") as FormArray } |
newSkill
method returns a skill
FormGroup
. It has two fields. Name of the skill
and years of exp
1 2 3 4 5 6 7 8 | newSkill(): FormGroup { return this.fb.group({ skill: '', exp: '', }) } |
addEmployeeSkill
method the skill
to employee
.
1 2 3 4 5 | addEmployeeSkill(empIndex:number) { this.employeeSkills(empIndex).push(this.newSkill()); } |
Finally, removeEmployeeSkill
method, which removes the skill
of an employee.
1 2 3 4 5 | removeEmployeeSkill(empIndex:number,skillIndex:number) { this.employeeSkills(empIndex).removeAt(skillIndex); } |
The onSubmit
method accepts the Employee Forms. You can validate and update the database from here.
1 2 3 4 5 | onSubmit() { console.log(this.empForm.value); } |
Template
Create a <form>
and bind it to empForm
using formgroup
directive
1 2 3 | <form [formGroup]="empForm" (ngSubmit)="onSubmit()"> |
Under empForm
we have employees
array. Bind it to the div
element using formArrayName
directive
1 2 3 | <div formArrayName="employees"> |
Next, loop through the controls under the employees
using ngFor. let empIndex=index
will save the index position in the empIndex
local variable.
1 2 3 4 | <div *ngFor="let employee of employees().controls; let empIndex=index"> |
The index is used as the name of the control in a Form Array. Hence use the [formGroupName]="empIndex"
to bind it to the FormGroup
. The style exists to show a nice border around employee
1 2 3 | <div [formGroupName]="empIndex" style="border: 1px solid blue; padding: 10px; width: 600px; margin: 5px;"> |
Input element for employee’s firstName
, lastName
. Also, place a button removeEmployee(empIndex)
to remove this employee from the array.
1 2 3 4 5 6 7 8 9 | {{empIndex}} First Name : <input type="text" formControlName="firstName"> Last Name: <input type="text" formControlName="lastName"> <button (click)="removeEmployee(empIndex)">Remove</button> |
Bind the skills of the empoyee to a div using formArrayName directive
1 2 3 | <div formArrayName="skills"> |
ngFor, now loops through the skills
array of the employee
.
1 2 3 | <div *ngFor="let skill of employeeSkills(empIndex).controls; let skillIndex=index"> |
input fields for skill and exp, Also button to remove the skill, which calls the removeEmployeeSkill(empIndex,skillIndex)
1 2 3 4 5 6 7 8 9 10 11 12 | <div [formGroupName]="skillIndex"> {{skillIndex}} Skill : <input type="text" formControlName="skill"> Exp: <input type="text" formControlName="exp"> <button (click)="removeEmployeeSkill(empIndex,skillIndex)">Remove</button> </div> |
Finally a button the add the skill to the employee
1 2 3 | <button type="button" (click)="addEmployeeSkill(empIndex)">Add Skill</button> |
Finally a button the add the employee
1 2 3 | <button type="button" (click)="addEmployee()">Add Employee</button> |
Updating the Nested forms With Initial Data
Setting the initial values in the nested Forms is a little tricky. You need to build the form dynamically and then use the PatchValue & SetValue methods to update the form.
SetValue & PatchValue in FormArray Angular
Source Code
You can view the source code from the StackBlitz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | import { Component, VERSION } from '@angular/core'; import { FormGroup, FormArray, FormBuilder } from '@angular/forms'; @Component({ selector: 'my-app', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { empForm: FormGroup; constructor(private fb: FormBuilder) {} ngOnInit() { this.empForm = this.fb.group({ employees: this.fb.array([]) }); } employees(): FormArray { return this.empForm.get('employees') as FormArray; } newEmployee(): FormGroup { return this.fb.group({ firstName: '', lastName: '', skills: this.fb.array([]) }); } addEmployee() { this.employees().push(this.newEmployee()); } removeEmployee(empIndex: number) { this.employees().removeAt(empIndex); } employeeSkills(empIndex: number): FormArray { return this.employees() .at(empIndex) .get('skills') as FormArray; } newSkill(): FormGroup { return this.fb.group({ skill: '', exp: '' }); } addEmployeeSkill(empIndex: number) { this.employeeSkills(empIndex).push(this.newSkill()); } removeEmployeeSkill(empIndex: number, skillIndex: number) { this.employeeSkills(empIndex).removeAt(skillIndex); } onSubmit() { console.log(this.empForm.value); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | <h1>Angular Nested FormArray / Dynamic FormArray</h1> <form [formGroup]="empForm" (ngSubmit)="onSubmit()"> <div formArrayName="employees"> <div *ngFor="let employee of employees().controls; let empIndex=index"> <div [formGroupName]="empIndex" style="border: 1px solid blue; padding: 10px; width: 600px; margin: 5px;" > {{empIndex}} First Name : <input type="text" formControlName="firstName" /> Last Name: <input type="text" formControlName="lastName" /> <button (click)="removeEmployee(empIndex)">Remove</button> <div formArrayName="skills"> <div *ngFor="let skill of employeeSkills(empIndex).controls; let skillIndex=index" > <div [formGroupName]="skillIndex"> {{skillIndex}} Skill : <input type="text" formControlName="skill" /> Exp: <input type="text" formControlName="exp" /> <button (click)="removeEmployeeSkill(empIndex,skillIndex)"> Remove </button> </div> </div> </div> <button type="button" (click)="addEmployeeSkill(empIndex)"> Add Skill </button> </div> </div> <button type="button" (click)="addEmployee()">Add Employee</button> </div> </form> {{this.empForm.value | json}} <br /><br /> <a href="https://www.tektutorialshub.com/angular/nested-formarray-example-add-form-fields-dynamically/" >Nested FormArray / Dynamic FormArray</a > |
Summary
In this tutorial, we learned how to create Multiple levels of nested forms in Angular.
Reference
Read More
- Angular Tutorial
- Angular Forms Tutorial: Fundamental & Concepts
- Template Driven Forms in Angular
- Set Value in Template Driven forms in Angular
- Reactive Forms in Angular
- FormBuilder in Reactive Forms
- SetValue & PatchValue in Angular
- StatusChanges in Angular Forms
- ValueChanges in Angular Forms
- FormControl
- FormGroup
- FormArray Example
- Build Dynamic or Nested Forms using FormArray
- SetValue & PatchValue in FormArray
- Select Options Dropdown
- Validations in Reactive Forms in Angular
- Custom Validator in Reactive Forms
- Passing Parameter to Custom Validator in Reactive Forms
- Inject Service into Custom Validator
- Validation in Template Driven Forms
- Custom Validator in Template Driven Forms
- Angular Async Validator
- Cross Field Validation
- Adding Validators Using SetValidators
Hi sir, this is very helpful to me . I used the nested form array and submitted data in database but now I need to bind the same data from database table by generating the form array to perform edit functionality. Please help me to achieve this
Nice article. Is there any example how to load data dynamically when first time load?
Thank you very much.
Initially I had done it another way and it worked, but with a version update it stopped working.
You are the best!
This is a great article and it helps me explore new things.
I have a question/doubt here..
If we want to use drop-down for each nested array with different selected value.
How it is possible !?
Could you please help on this scenario.
i am getting this error Cannot read property ‘controls’ of null
Compare your code with that from StackBlitz. Link added in the post.
this example is good. However I have an existing dataset in the same format being received from a service method. I need to provide the capability to add/delete/modify. The delete/modify should work on existing data received from service as well. How do I render the existing data set? Any suggestion.
hi sir,
it is very useful but how to get value and set value from dynamic group???
thank you so much, it was soo helpful
Please upload the code to gitHub or stackblitz. It will much easier check
great walkthrough. Thank you. I was wondering how to add infinite employee forms to each other. I have a list of commands, and each can have a subcommands. Subcommands can also have subcommands as well until the flow is met. Any suggestions on how it can be done? thanks again
“infinite” depth sub-commands where depth is unknown would be tricky. I think you should go with Nested Components along with the Nested FormArray.
I hope this helps
I don’t expect the number of sun-commands to be very large. However, I also can’t determine the number of sub-commands. Thus, having the option to add as many sub-commands as it’s needed is quite what i need. I don’t see the use of sub components since i need to use the exact same form. What do you think? any thoughts on how that can be implemented.
Thanks,
The child components can share the parent form. You can pass it to child component using @Input just like you pass the variable
https://medium.com/@joshblf/dynamic-nested-reactive-forms-in-angular-654c1d4a769a