Tutorial

How To Dynamically Create Form Fields with FormArray in Angular

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:

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:

src/app/app.module.ts
// ...
import { ReactiveFormsModule } from '@angular/forms';

Also, add ReactiveFormsModule to the module’s array of imports:

src/app/app.module.ts
@NgModule({
  ...
  imports: [
    ...
    ReactiveFormsModule
  ]
  ...
})

In app.component.ts, add an import of FormBuilder, FormGroup, and FormArray from the Angular forms module:

src/app/app.component.ts
// ...
import { FormBuilder, FormGroup, FormArray } from '@angular/forms';

Next, you will initialize the form using FormBuilder in the ngOnInit hook:

src/app/app.component.ts
// ...

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:

src/app/app.component.ts
// ...

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:

src/app/app.component.ts
// ...

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:

src/app/app.component.html
<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:

src/app/app.component.html
<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():

src/app/app.component.html
<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:

src/app/app.component.html
<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.

Creative Commons License