Skip to content

Creating Dynamic Forms

It is not an uncommon situation to require a dynamic form — one that does not just have a static/pre-defined number of controls. For example, let’s say you have a form that allows a user to enter a guest list. Each guest should be entered into their own text field, e.g:

Dynamic guest form

We want to be able to click the Add guest button to add as many fields for guests as we want. But, when we define forms like this:

guestForm = this.fb.nonNullable.group(
{
guests: ['', Validators.required],
},
);

How can we account for this dynamic behaviour?

Introducing Form Arrays

We have been focusing on the concept of a FormControl which is used for an individual field in our forms, and a FormGroup which is a collection of those controls. Another feature we can make use of is FormArray. A FormArray is an array of multiple controls and the values of all of those controls will be output as an array when we check the form’s value. Importantly, a FormArray provides us a way to easily push new controls into this FormArray dynamically.

It might sound complex, but it isn’t all that different to what we have been doing already. Let’s see how we might implement our guests example. First, we would add another control to our form, but we will set it to be a FormArray:

myForm = this.fb.nonNullable.group(
{
username: ['', Validators.required, usernameAvailableValidator],
age: [null, adultValidator],
password: ['', [Validators.minLength(8), Validators.required]],
confirmPassword: ['', [Validators.required]],
guests: this.fb.array([]),
},
{
validators: [passwordMatchesValidator],
}
);

Now we will create a method that will push new controls into this array:

addGuest() {
const guestControl = this.fb.control('', Validators.required);
this.myForm.controls.guests.push(guestControl);
}

Every time we call this addGuest method, it is going to push a new control into the guests FormArray. Now we will need to make some modifications to our template:

<div>
<h2>Add Guests</h2>
<ng-container formArrayName="guests">
@for(guest of myForm.controls.guests.controls; let i = $index; track
i){
<input [formControlName]="i" type="text" />
}
</ng-container>
<button (click)="addGuest()">Add</button>
</div>

We have added an Add Guests section inside of our <form>. We create a containing element to assign a formArrayName — this is the same general idea as a formControlName but it will tie the children of this element to the guests array that we created in myForm.

Then, we need to render out an <input> for every one of the controls in the array. On top of this, we also get the index of our @for loop so we can use that as a unique formControlName for each of our controls that are dynamically added.

Now, if we fill in the form and check its value we should see something like this:

Guest data

Our guests property is an array of any of the guests we dynamically added, using the index as the property name for each guest.