Featured Image
Software Development

Add/Push and Remove Form fields dynamically to FormArray with Reactive Forms in Angular

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.

Understanding FormArray in Angular

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:

  • Dynamic surveys where questions can be added or removed.
  • E-commerce product listings with customizable options.
  • Complex registration forms with optional sections.

In this guide, we will cover the following:

  • How to add/push FormControls to a FormArray dynamically.
  • Remove an item from a FormArray and manage dynamic updates. 
  • Implement a custom validator to ensure at least one checkbox is selected.

About Reactive Forms in Angular

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:

  • FormControl: This signifies an individual form element, such as an input field, checkbox, or dropdown.
  • FormGroup: It acts as a vessel for multiple FormControl elements, which allows you to manage related form elements together.
  • FormArray: It signifies a dynamic array that has the capacity to contain multiple FormControl or FormGroup elements, thereby becoming the best possible option for managing variable-length lists, such as a dynamic list of checkboxes or form fields.

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.

Adding FormControl to FormArray Dynamically

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.

Create a FormGroup in Angular

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.

dynamic-form.component.ts

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:

  1. demoForm is our FormGroup which will have the form fields.
  2. arrayItems is an array of object which contains an id and title of the object. We will use the title as a label for our checkbox.
  3. The FormBuilder service is injected into the constructor to simplify the creation and management of form controls.
  4. demoForm is initialized with a FormGroup that contains a single control of type FormArray named demoArray. This FormArray is initialized as an empty array without validators (we will add a custom validator later).
  5. The arrayItems property is initialized as an empty array in the ngOnInit() lifecycle hook.

Binding the FormGroup to the Template:

In the template file, we’ll bind our FormGroup to the form and iterate through `arrayItems` to display a list of checkboxes.

dynamic-form.component.html

<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>
  1. Binding of demoForm with the formGroup directive, which applies to the <form> tag.
  2. Binding of demoArray with the formArrayName directive. It also iterates over arrayItems, allowing us to display the same number of checkboxes as there are items in the array. It also uses i as an index.
  3. Binding of the checkbox control to demoArray[i] using the formControl directive. It uses the index ‘i’ to get the FormControl for that specific checkbox.
  4. The label for selecting a checkbox uses the title as its label.
  5. We also used the id and for properties on the <input> and <label> tags, respectively, and bound those properties to the id of the arrayItem. This allows a checkbox to be checked or unchecked when someone clicks on the label.

Add and Remove FormControl Dynamically:

Now, let’s look at how to add FormControl to FormArray dynamically or remove them using methods in our component.

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.control(false));
   }

   removeItem() {
      this.arrayItems.pop();
      this.demoArray.removeAt(this.demoArray.length - 1);
   }
   
   ...
   ...
}
  • We use the getter method to retrieve demoArray as a FormArray directly from demoForm. This creates a new property named demoArray, allowing us to access the array of controls directly without having to reference the FormGroup (demoForm).
  • A method is created that adds an item to the end of arrayItems and also adds a new FormControl to the end of demoArray, initializing it with false so that the checkbox remains unchecked initially.
  • A method is created that removes an item from the end of arrayItems and also removes the corresponding FormControl from the end of demoArray.

Also read: Building Web Applications with Angular and Atomic Design Principles

Custom Validator for FormArray

Finally, let’s implement a custom validator to ensure at least one checkbox is selected.

dynamic-form.component.ts:

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.

Handling Complex Forms with FormGroup and FormArray

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:

  • Add entire FormGroup to FormArray to manage multiple form sections.
 
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();

}));

   }

   

   ...

   ...

}

  • Remove FormGroup from FormArray when users delete form sections dynamically.
 
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.

Error Handling and Best Practices for Large Forms

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: 

  1. Lazy Load Controls

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.

  1. Modularize Forms

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.

  1. Track Changes

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.

  1. Implement Robust Error Handling

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.

Conclusion

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.

author
Kalpesh Shingala
I am a Fullstack Developer having expertise in architecture and development of Responsive, Performance efficient, Extendable and Browser compatible web applications by using the latest technologies like Angular and Vuejs.