Angular Reactive Forms: The Ultimate Guide to FormArray
In this article, I’d like to discuss this peculiar creation — a FormArray
, exposed by Angular Reactive Forms. We’ll learn when and how to use it, both in the component and in the template. Moreover, we’ll see how we can apply custom validation to it.
This article assumes that you have a least some working knowledge of Angular FormGroup
and FormControl
. Let’s get started:
Why Do We Need a FormArray
As you probably know, when we create an AbsractControl
such as FormGroup
or a FormControl
, we can use an array as our value. For example:
That’s nice, but unless you’re dealing with a value accessor that knows how to work with a plain array, it’s probably useless. In most cases, what we want is to employ the Angular API to maintain each item in the array individually; We want Angular to sync the value and to use the validators API.
That’s when a FormArray
comes in handy.
What’s a FormArray
A FormArray
is responsible for managing a collection of AbstractControl
, which can be a FormGroup
, a FormControl
, or another FormArray
.
Just like a FormGroup
, which groups AbstractControl
objects in an object, a FormArray
does the same but in an array. Angular exposes specific APIs to help you manage this collection, which we’ll delve into later on.
Now that we understand what it is, let’s see how we can use it.
Working with a FormArray
Let’s say we need to display a form where users can add, edit, or remove from a list of skills:
We create a FormArray
instance and pass an empty array as the initial state value. Let’s see what the skills
property contains:
Much like a FormControl
and FormGroup
, FormArray
extends the AbstractControl
class. As a result, we can see properties it has in common with them, such as valid
, dirty
, disabled
, etc.
In addition, it has a property named controls
, which, as we noted, holds an array that can be populated with instances of AbstractControl
.
Now, let’s add a new skill to our collection:
Each time we invoke the addSkill
method, we push a new FormControl
to the controls
array. Let’s use it in the template. First, we need to loop over the FormArray
controls property:
Then, we pass each control to the formControl
directive so that we can sync each control to a corresponding element:
It’s as simple as that. Let’s see what other built-in methods are at our disposal:
removeAt(index)
:
This method takes an index
and removes the matching AbstractControl
. Under the hood, it just calls the native splice
method:
insert(index, AbstractControl)
:
The opposite of the removeAt()
method. It inserts a new AbstractControl
at the given index
in the controls array:
clear()
:
Removes all the elements from the array:
setControl(index, AbstractControl)
:
Unlike the insert
method, it replaces an existing control with the provided one. In this example it’s used in a replace()
method, which replaces the first control with a newly created one:
at(index)
:
Returns the AbstractControl
at the given index
in the array. In this example it’s used to return the first control:
Insert a FromGroup
In the previous section, we saw how we manage a collection of FormControls
; Now let’s see how we manage FormGroup
objects within a FormArray
:
Instead of passing a FormControl
, we pass a FormGroup
. Now let’s use it in the template:
We loop over each control, which in this example is a FormGroup
, and pass it to the formGroup
directive. Then, it’s just a matter of syncing the FormGroup
controls in the way we’re already familiar with — passing the relevant control name to the formControlName
directive.
Using a FormArray in a FormGroup
Now that we understand the basics, let’s look at some more practical examples, where we usually employ a FormGroup
that contains a FormArray
:
Nothing special here, just a regular process of building a FormGroup
. Let’s take a look at the template:
We apply the formGroup
directive to the form in the template, and inside it, we bind the name
control via the formControlName
directive. Similarly, We’d like to bind the controls of our FormArray
. To do so, we need to follow 3 steps:
First, we need to apply the formArrayName
to a container element. Under the hood, it’s the same as running user.get('skills')
.
Next, like the previous example, we need to loop over the FormArray
controls:
Before discussing the final step, let’s define the skills
property in our component. The traditional way to do it is by using a getter
function that obtains a reference to the FormArray
control from the parent FormGroup
:
Or we can hold it in a property
:
Note that because the returned control is of the type AbstractControl
, we need explicitly list the type, in order to get access to the method syntax for the FormArray
instance.
Now, for the final step:
The formControlName
directive takes the name of the control that we want to sync with to our form element. When we work with a FormGroup
we pass the corresponding object key name, for example:
This will search for user.get('name')
. But in our case, as we’re dealing with a FormArray
— the name is, in fact, the index. If you think about it, it makes sense; In JS, Arrays are a special object whose keys are numeric (based on their index). So eventually, all this code does is something akin to the following:
user.get('skills')[index] => FormControl
Now, let’s see the same process but with a FormArray
that contains a collection of FormGroups
:
Since the FormArray
now holds FormGroup
objects, we need to use the formGroupName
directive:
user.get('skills')[index] => FormGroup
Then, when we use the formControlName
, it’ll search for the closest ControlContainer
parent, which in this case is the current parent FormGroup
object.
FormArray Validation
As we mention in the beginning, we can apply validation to each one of the AbstractControl
as we’d typically do:
In this case, the FormArray
validity status is determined based on its AbstractControls
validity. So as long as there’s a single invalid AbstractControl
, the FormArray
valid property will be set to false
.
We can also apply a validation at the FormArray
level. Let’s say that we need to validate the array size:
As with any AbstractControl
, we can pass a validator
function, which in our case receives the FormArray
instance on each change, and should return null
when that array is valid
; Otherwise, it returns an object
which indicates the nature of the error.
Populating FormArrays with Server Data
Let’s wrap things up with a common example where we need to populate the FormArray
controls with data which arrives from the server.
In this case, we can’t use patchValue()
or setValue()
, as these methods are meant for updating the value of existing controls, and the controls which correspond to the data that’s arrived haven’t been created yet; Instead, we need to create the FormArray
from scratch, and set the value for each of its controls:
In case we already have existing controls in our FormArray
, we can simply create the controls we need to add, and push them to the array:
🚀 In Case You Missed It
Here are a few of my open source projects:
- Akita: State Management Tailored-Made for JS Applications
- Spectator: A Powerful Tool to Simplify Your Angular Tests
- Transloco: The Internationalization library Angular
- Forms Manger: The Foundation for Proper Form Management in Angular
Follow me on Medium or Twitter to read more about Angular, Akita and JS!