Let's keep this short and sweet. The Form Array [1] is a powerful feature within Angular which enables engineers to manage form controls as elements in an array [2]. Ultimately, think of it as an array, that's really all it is, however it also gives us a lot of flexibility when building highly complex forms that require dynamic interaction such as creating and deleting form controls (text inputs, radio buttons, etc). This feature was instrumental in my implementation of a highly complex administration interface for an Investment Bank that required full configuration of the bank's data hierarchy, including creating, editing and deletion of members of this hierarchy.

formArray.push(formControl) - If you understand this, then you can understand form arrays quite quickly.

Show me the code

We'll begin with an example, see the example below of a Form Array; The first code snippet entails using Angular's form builder to firstly construct the form which will contain our form controls, form groups and form arrays. Although, using the form builder is optional and you also can instantiate the formControl, formGroup and formArray from their respective classes individually to achieve this as shown here [2].


  constructor(private fb: FormBuilder) {
    // Build our form as per the code below. 
  }
  

// Use Angular's Form Builder (aliases as fb 'fb: Formbuilde') to construct a new form
userForm = this.fb.group({
  firstName: ['', Validators.required],
  lastName: [''],
  address: this.fb.group({
    postcode: [''],
    city: [''],
    county: ['']
  }),
  children: this.fb.array([]) // Our array of formControls for a user's children, they can generate as many form controls for their different children as they want.
});
}

Accessing and using the form array

At this point, I would like to make an important observation about the Form Array. As shown in the above example of our children Form Array, we can see that it is used as a Form Control, much like the address Form Group is. This is because the Form Control, Form Group and Form Array classes extend from the same base class AbstractControl [3]. This means that Form Arrays and Form Groups can be used as Form Controls, providing immense flexibility as Form Arrays can have nested Form Arrays and so on, allowing engineers to build out massive forms in a clear and consice manner. Now we know this, we can move onto how to access and use Form Arrays.


get children() {
  return this.userForm.get('children') as FormArray;
}


The Angular team demonstrate the leveraging of TypeScript's type assertions (in the Angular documentation [4]) and getters to access the Form Array. This is because the default return type of the Form Array is AbstractControl which allows the Form Array to be a normal form control (as discussed earlier), but does not provide access to the FormArray class itself. This means that it cannot be used as a Form Array until using type assertion to enforce the returned AbstractControl to inherit FormArray.

The reason a getter is used, is to allow the following syntax when interacting with the Form Array:


`this.children.push(this.fb.control(''));`

You don't need to use a getter to use/access it, but it's much cleaner in my opinion. You will have to use the array push() method on a variable containing to the returned value of this.userForm.get('children') as FormArray in this case, which will look something like:


// Using a getter
get children() {
  return this.userForm.get('children') as FormArray;
}

this.children.push(this.fb.control(''));
----------------------------------------------------------------------
// Using a variable
const myChildrenFormArray = this.userForm.get('children') as FormArray;

myChildrenFormArray.push(this.fb.control(''));


The rest is a breeze


<div formArrayName="children">
  <button (click)="addChild()">Add Child</button>
  <button (click)="removeChild()">Remove Child</button>

<!-- This will loop through all children controls in the children Form Array -->
  <div *ngFor="let child of children.controls; let i=index">
    <label>
      Alias:
      <input type="text" [formControlName]="i">
    </label>
  </div>
</div>


Add a control

To add a child control, just as we went through earlier, simply push a new control into the Form Array:


addChild(): void {
    this.children.push(this.fb.control(''));
}


Remove a control

To remove a child control, use the FormArray class method removeAt(i) [5], specifying the index in the array that you would like to remove. Alternatively if you would like to remove the last element in the array, use the generic array splice method.:


removeChild(): void {
    this.children.removeAt(4); // Removes the control at index four in the array. 
}


That's it

The Angular documentation and source code is full of more implementational and class specifics, but I hope this has helped you to understand and use Form Arrays on an everyday practical/theoretical level.

References:

  • [1] https://angular.io/api/forms/FormArray
  • [2] https://angular.io/api/forms/FormControl
  • [3] https://angular.io/api/forms/AbstractControl
  • [4] https://angular.io/guide/reactive-forms#step-3-accessing-the-formarray-control
  • [5] https://angular.io/api/forms/FormArray#removeat