Reactive forms in Angular are very powerful for handling changes in value, checking statuses, and performing validations on form fields within a particular form, thanks to support for FormGroup, FormControl, and FormArray.
However, it can sometimes be difficult to organize the hierarchy inside FormGroup and FormArray. There might be situations where we need to dynamically add a FormArray to FormGroup after completing certain operations, such as an API call, and we may also need to remove an item from a FormArray in Angular.
This article explains how we can dynamically add and remove checkboxes to and from FormArray, and also how to create a custom validator that checks whether at least one checkbox is selected. Let’s get started.
One of the key elements in Reactive Forms is a FormArray. It enables the management of a dynamic collection of form controls. With its working similar to an array, it also has features where you can push FormGroup to FormArray and add controls dynamically, such as FormGroup or FormControl. This kind of flexible approach and structure is priceless when it comes to applications where the quantity of form fields is dynamic, including basics like adding/removing items in a list or enabling/disabling checkboxes based on user interaction.
Also, since FormArray offers complete support for real-time updates, you can add controls to FormArray, adjust current ones, or remove a FormGroup from a FormArray dynamically. However, all of this can be done in coherence with the logic of your application. This becomes significantly important in scenarios like:
In this guide, we will cover the following:
Reactive Forms in Angular offer a sturdy and reliable way to manage forms and form validation by utilizing a model-driven approach. The template-driven forms are more upfront and direct with a considerable lack of flexibility. However, Reactive Forms offer dynamic solutions by allowing you to build complex and scalable forms. This can be done by defining the form model in the component class, thus making it easier and more convenient to manage the form’s state, validate user inputs, and dynamically add or remove form controls, for instance, the ones in a FormArray.
The core building blocks of Reactive Forms are:
In this blog, our primary focus is on using FormArray within Reactive Forms to add/push controls dynamically and also manage their addition and removal in real-time. Reactive Forms in Angular are the most effective when it comes to form management whether you’re building a form that assembles several items, adding checkboxes, or customizing product options.
It is vital to know that not only can you add individual FormControl to a FormArray but also an entire FormGroup structure(s). This feature is primarily useful when you are working with complex forms, for example, when multiple fields need to be added or removed dynamically.
We will use a component named `DynamicFormComponent` to render our form. Before we dive into the code, ensure you have an existing Angular project and a component to render the form.
import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, FormArray } from '@angular/forms'; @Component({ selector: 'app-dynamic-form', templateUrl: './dynamic-form.component.html', styleUrls: ['./dynamic-form.component.css'] }) export class DynamicFormComponent implements OnInit { demoForm: FormGroup; arrayItems: { id: number; title: string; }[]; constructor(private _formBuilder: FormBuilder) { this.demoForm = this._formBuilder.group({ demoArray: this._formBuilder.array([]) }); } ngOnInit() { this.arrayItems = []; } }
Declaration of two properties demoForm and arrayItems:
In the template file, we’ll bind our FormGroup to the form and iterate through `arrayItems` to display a list of checkboxes.
<form [formGroup]="demoForm"> <div formArrayName="demoArray" *ngFor="let arrayItem of arrayItems; let i=index"> <input [id]="arrayItem.id" type="checkbox" [formControl]="demoArray[i]"> <label [for]="arrayItem.id" class="array-item-title"> {{arrayItem.title}}</label> </div> </form>
Now, let’s look at how to add FormControl to FormArray dynamically or remove them using methods in our component.
... ... export class DynamicFormComponent implements OnInit { ... ... get demoArray() { return this.demoForm.get('demoArray') as FormArray; } addItem(item) { this.arrayItems.push(item); this.demoArray.push(this._formBuilder.control(false)); } removeItem() { this.arrayItems.pop(); this.demoArray.removeAt(this.demoArray.length - 1); } ... ... }
Also read: Building Web Applications with Angular and Atomic Design Principles
Finally, let’s implement a custom validator to ensure at least one checkbox is selected.
import { FormBuilder, FormGroup, FormArray, ValidatorFn } from '@angular/forms'; ... ... export class DynamicFormComponent implements OnInit { constructor(private _formBuilder: FormBuilder) { this.demoForm = this._formBuilder.group({ demoArray: this._formBuilder.array( [],this.minSelectedCheckboxes() ) }); } ... ... minSelectedCheckboxes(): ValidatorFn { const validator: ValidatorFn = (formArray: FormArray) => { const selectedCount = formArray.controls .map(control => control.value) .reduce((prev, next) => next ? prev + next : prev, 0); return selectedCount >= 1 ? null : { notSelected: true }; }; return validator; } }
Import ValidatorFn.
Add a custom validator to the FormArray demoArray.
Declare the custom validator function minSelectedCheckboxes(). When this validator is applied to the FormArray (in this case, demoArray), it will automatically receive the FormArray and all its controls. The validator iterates through the controls inside the FormArray, counting the number of checked (true) checkboxes. If one or more checkboxes are checked, it returns null, indicating that the FormArray is valid. Otherwise, it returns an object with the property notSelected set to true, indicating that no checkbox is selected.
That’s it. Just as we added and removed a FormControl, you can also add and remove an entire FormGroup to/from a FormArray and apply a custom validator to validate that FormGroup.
In more advanced scenarios, you might need to add FormArray to FormGroup dynamically or work with nested groups of controls. Angular’s Reactive Forms make this easy by providing a flexible API for building forms on the fly. For instance, you can:
dynamic-form.component.ts: ... ... export class DynamicFormComponent implements OnInit { ... ... get demoArray() { return this.demoForm.get('demoArray') as FormArray; } addItem(item) { this.arrayItems.push(item); this.demoArray.push(this._formBuilder.group({ name: this._formBuilder.control(); email: this._formBuilder.control(); })); } ... ... }
removeItem() { this.arrayItems.pop(); this.demoArray.removeAt(this.demoArray.length - 1); }
This flexible approach of working aids in building complex, multi-step forms, as well as many other dynamic structures with utter ease and convenience.
Whilst it is doable, at times managing huge forms with multiple FormGroups and FormArrays can become complicated. Below are the best practices to follow for handling these complexities efficiently and to ensure a smooth user experience:
One of the most basic principles to follow for performance optimization would be to consider loading form controls only when essential, especially if many controls are there in forms. This will show remarkable improvement in initial load times and also reduce the complexity of form management.
The complex large forms, if broken down into smaller more manageable fragments, will help manage the overall complexity and become easy to maintain with reusable components. With this modular structure in place, each component is able to handle its own FormGroup or FormArray, which makes it easier to manage and test individual parts of the form.
Tracking changes can be easily achieved if you utilize Angular’s valueChanges and statusChanges trackers to monitor and revert changes in form controls. This step enables the implementation of features like dynamic validation, conditional logic, or real-time feedback based on user interactions.
By implementing strong error-handling tactics, you can effectively handle errors in large forms. Try making use of Angular’s form control errors to offer insightful feedback to users and ensure that all validation errors are accurately exhibited.
The integration of these practices in your methodology will help you manage large forms more effectively, ensuring a better user experience and a more maintainable codebase.
Dynamic form handling in Angular has become simpler and more convenient by using FormArray and FormGroup. This way you can effortlessly add and remove form controls dynamically. It also helps apply custom validators to make sure that user inputs are in alignment with certain criteria.
If you want to scale up and take your Angular projects to the next level, we are here to support your journey by providing seamless form management, validation strategies, and responsive web applications. Connect with us today to augment your web app’s performance and user experience through our experienced Angular development services.