Tutorial
How To Dynamically Create Form Fields with FormArray in Angular
Introduction
In Angular 2+, Reactive Forms are available to manage the state of a form. FormArray
is used to track the value and validity state of form fields. You can use FormArray
in Reactive Forms to add form fields dynamically from a response to a user event.
FormArray
is used as an array that wraps around an arbitrary amount of FormControl
, FormGroup
, or even other FormArray
instances. With FormArray
you can add new form fields or a set of form fields declaratively.
In this article, we will go over an example application consisting of an order form for purchasing items. We will design this form to append a new form field for users to add items to their order.
Prerequisites
To follow along with this article, you will need:
- Node.js installed locally, which you can do by following How to Install Node.js and Create a Local Development Environment.
- A basic understanding of Angular. To learn more about Angular, check out the Angular topic page.
This post also assumes you are building from a fresh Angular project generated by @angular/cli
. You can refer to this post if you’re getting started with Angular CLI.
Step 1 — Importing FormArray
and Initializing the Form
First, ensure that you are importing ReactiveFormsModule
in your application.
In app.module.ts
, add an import
for ReactiveFormsModule
from the Angular forms
module:
// ...
import { ReactiveFormsModule } from '@angular/forms';
Also, add ReactiveFormsModule
to the module’s array of imports
:
@NgModule({
...
imports: [
...
ReactiveFormsModule
]
...
})
In app.component.ts
, add an import
of FormBuilder
, FormGroup
, and FormArray
from the Angular forms
module:
// ...
import { FormBuilder, FormGroup, FormArray } from '@angular/forms';
Next, you will initialize the form using FormBuilder
in the ngOnInit
hook:
// ...
export class AppComponent {
orderForm: FormGroup;
items: FormArray;
constructor(private formBuilder: FormBuilder) {}
ngOnInit() {
this.orderForm = this.formBuilder.group({
customerName: '',
email: '',
items: this.formBuilder.array([ this.createItem() ])
});
}
}
In this example, orderForm
will consist of a customerName
, email
, and an array of items
.
Notice that the items
instance is a FormArray
instead of a FormControl
. We’re calling a createItem
method to create a FormGroup
as the first item in our array.
Next, we will add the createItem
method to the AppComponent
:
// ...
export class AppComponent {
// ...
createItem(): FormGroup {
return this.formBuilder.group({
name: '',
description: '',
price: ''
});
}
}
In our example, an item
will consist of a name
, description
, and a price
.
Now, we have an orderForm
and items
. We still have to implement a way to dynamically add new items.
Step 2 — Adding to the FormArray
Dynamically
We can treat our FormArray
like a regular array and push new items into it. Add the addItem
method to the AppComponent
:
// ...
export class AppComponent {
// ...
addItem(): void {
this.items = this.orderForm.get('items') as FormArray;
this.items.push(this.createItem());
}
}
Now we have addItem()
defined. We still have to call addItem
method in the template when the user clicks to add a new item.
Let’s use the formArrayName
directive in the template to bind to the FormArray
. In app.component.html
, replace the content with our new template:
<form [formGroup]="orderForm">
<div
formArrayName="items"
*ngFor="let item of orderForm.get('items')['controls']; let i = index;"
>
...
</div>
...
</form>
Next, let’s add our FormGroup
of FormControls
for an item
inside of the FormArray
:
<form [formGroup]="orderForm">
<div
formArrayName="items"
*ngFor="let item of orderForm.get('items')['controls']; let i = index;"
>
<div [formGroupName]="i">
<input formControlName="name" placeholder="Item name">
<input formControlName="description" placeholder="Item description">
<input formControlName="price" placeholder="Item price">
</div>
</div>
...
</form>
Notice how the formGroupName
directives now take an index instead of a name. We set it using the index that ngFor
gives us.
After the FormArray
, let’s add a button that when clicked calls addItem()
:
<form [formGroup]="orderForm">
<div
formArrayName="items"
*ngFor="let item of orderForm.get('items')['controls']; let i = index;"
>
<div [formGroupName]="i">
<input formControlName="name" placeholder="Item name">
<input formControlName="description" placeholder="Item description">
<input formControlName="price" placeholder="Item price">
</div>
</div>
<button type="button" (click)="addItem()">Add Item</button>
</form>
For debugging purposes, we can add some code to reveal FormControl
’s value in the template by traversing our form:
<form [formGroup]="orderForm">
<div
formArrayName="items"
*ngFor="let item of orderForm.get('items')['controls']; let i = index;"
>
<div [formGroupName]="i">
<input formControlName="name" placeholder="Item name">
<input formControlName="description" placeholder="Item description">
<input formControlName="price" placeholder="Item price">
</div>
Exposed item name: {{ orderForm.controls.items.controls[i].controls.name.value }}
</div>
<button type="button" (click)="addItem()">Add Item</button>
</form>
At this point, we have a form that starts with a single item. Once we enter a name
, description
, and price
for the first item, we can click the Add Item button and a new item is dynamically appended to the form.
Conclusion
You have completed an exploration of how Angular 2+ Reactive Forms and FormArray
can be used to add new form fields dynamically. This pattern is useful for scenarios where a user may need to enter data more than once.
If you’d like to learn more about Angular, check out our Angular topic page for exercises and programming projects.