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:
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:
Our guests property is an array of any of the guests we dynamically added,
using the index as the property name for each guest.