Understanding Angular's FormArray
Angular's FormArray
is a powerful tool in reactive forms (ReactiveFormsModule
) that allows us to manage a dynamic set of form controls. Think of it as a flexible container for managing an array of form controls, particularly useful for forms where the number of inputs is variable, like a list of tasks, addresses, or phone numbers.
All code can be found here: AngularFormArrayTutorial - Github
Angular 19 Documentation: Reactive Forms Docs
What is FormArray?
A FormArray
is similar to an array, but instead of storing regular values, it holds FormControl
, FormGroup
, or even other FormArray
. Here's a breakdown:
- FormControl: Manages a single value.
- FormGroup: Manages a group of controls.
- FormArray: Manages dynamic controls.
How to Use FormArray
Let’s explore a task management app example where FormArray
handles dynamic tasks and their subtasks.
1. Initializing a FormArray
We start by creating a FormGroup
containing a FormArray
for managing tasks. Each task is represented as a FormGroup
that includes a taskName
FormControl
and a subtasks
FormArray
.
constructor(private fb: FormBuilder) {
this.tasksForm = this.fb.group({
tasks: this.fb.array([]), // FormArray to manage tasks
});
}
2. Accessing the FormArray
To make the FormArray
easily accessible, we define a getter:
// Getter for the tasks FormArray
get tasks(): FormArray {
return this.tasksForm.get('tasks') as FormArray;
}
// Helper to get subtasks FormArray for a specific task
subtasks(index: number): FormArray {
return (this.tasks.at(index) as FormGroup).get('subtasks') as FormArray;
}
3. Adding New Controls Dynamically
To add a new task, we push a new FormGroup
to the tasks
FormArray
. Each FormGroup
includes:
- A
taskName
FormControl
. - A
subtasks
FormArray
for nested subtasks.
addTask(taskName: string = '') {
const taskGroup = this.fb.group({
taskName: taskName || '', // Default to an empty string
subtasks: this.fb.array([]), // Initialize an empty FormArray for subtasks
});
this.tasks.push(taskGroup); // Add the task to the tasks array
}
Section 4: Adding Subtasks
To add a subtask to a specific task, we retrieve the subtasks
FormArray
for that task and push a new FormControl
:
/*Uses the subtasks helper to get the subtask array, and then adds the new subtask at the end of the array. You could provide a starting value with another input, but for simplicities sake, we are pushing an empty string.*/
addSubtask(taskIndex: number, subtaskName: string = '') {
this.subtasks(taskIndex).push(this.fb.control(subtaskName || ''));
}
5. Removing Controls Dynamically
Removing tasks or subtasks with the the removeAt()
method:
- To remove a task:
removeTask(index: number) {
this.tasks.removeAt(index);
}
//Uses the subtasks helper to get the subtask array, and then removes the subtask at the subtaskIndex
removeSubtask(taskIndex: number, subtaskIndex: number) {
this.subtasks(taskIndex).removeAt(subtaskIndex);
}
6. Template for Dynamic Tasks and Subtasks
Now let’s take a look at the HTML template that binds to the FormArray
we’ve created in the component.
<form [formGroup]="tasksForm" class="tasks-form">
<div formArrayName="tasks" class="tasks-list">
@for (task of tasks.controls; track task; let i = $index) {
<div [formGroupName]="i" class="task-item">
<!-- Task Name -->
<div class="main-task">
<mat-form-field appearance="outline" class="task-field">
<input matInput placeholder="Task Name" formControlName="taskName" />
</mat-form-field>
<button
mat-mini-fab
color="warn"
(click)="removeTask(i)"
class="delete-task-btn"
>
<mat-icon>check</mat-icon>
</button>
</div>
<!-- Subtasks -->
<div formArrayName="subtasks" class="subtasks-list">
@for ( subtask of subtasks(i).controls; track subtask; let j = $index )
{
<div class="subtask-item">
<mat-icon class="arrow">arrow_right</mat-icon>
<mat-form-field appearance="outline" class="subtask-field">
<input matInput placeholder="Subtask Name" [formControlName]="j" />
</mat-form-field>
<button
mat-mini-fab
color="warn"
(click)="removeSubtask(i, j)"
class="delete-subtask-btn"
>
<mat-icon>check</mat-icon>
</button>
</div>
}
<!-- Add Subtask Button -->
<button
mat-raised-button
color="primary"
(click)="addSubtask(i)"
class="add-subtask-btn"
>
Add Subtask
</button>
</div>
</div>
}
</div>
<!-- Actions -->
<div class="form-actions">
<button mat-raised-button color="primary" (click)="loadSampleTasks()">
Load Sample Tasks
</button>
<button mat-raised-button color="accent" (click)="clearAllTasks()">
Clear Tasks
</button>
<button mat-raised-button color="primary" (click)="addTask()">
Add Task
</button>
</div>
</form>
<!-- Displays the forms current data in real time json format. Really helpful for debugging.-->
<pre>{{ tasksForm.value | json }}</pre>
Conclusion
The FormArray
is an essential part of Angular's reactive forms when working with dynamic form controls. Whether you're building complex forms with nested controls or simple dynamic lists, FormArray
provides a robust solution to handle the dynamic nature of your form controls in Angular.
Do you have an article you'd like to see? Let me know in the comments!
Top comments (0)