Search code examples
angularangular-reactive-formsangular-pipeangular-changedetection

Best way to render nested FormArrays via *ngFor


I've been doing a project with huge forms lately and I've run into performance problems. After some researching, I wondered, what is the better way to select nested FormArrays from a reactive form into the *ngFor statement.

For example, I have such a structure of the form, where ... means N items of FormGroup (inside FormArray) and FormControl (inside FormGroups):

MainFormGroup {
    ParentFormArray {
       ...
       FormGroup {
           ...
           NestedFormArray {
                ...
                FormGroup { ... }
                ...
           }
           ...
       }
       ...
    }
}

In Angular 15 I can't render FormArrays this way, as it can be found on older StackOverflow questions:

<div *ngFor="let item of form.controls['ParentFormArray'].controls; let i = index"

Because controls does not exist inside AbstractControl. So we need to tell template that ParentFormArray is an instance of FormArray directly.

To do that, we have several options (these only the ones I've found):

  1. make a getter in .ts that returns FormArray, but this won't allow us to put a parameter inside (for example, parent FormGroup of the NestedFormArray
  2. make a method that returns FormArray and can get a parameter to return nested FormArrays of a specific FormGroup (which is also nested inside parent FormArray)
  3. make a pipe for getting these items as FormArray

As I know, first two options are bad, because getter and method will be called on every ChangeDetection run. But what about pipes? Is this option more preferable in case of reactive forms?

For example pipe I've written for testing purposes:

@Pipe({ name: "getArray", pure: true })
export class GetArrayPipe {
    transform (formGroup: AbstractControl, path?: string): FormArray | null {
        return path ? <FormArray>formGroup.get(path) : <FormArray>formGroup;
    }
}

And using it like this:

for parent:
<accordion *ngFor="let parentItem of (ParentFormArray | getArray).controls; let i = index"> // parentItem there would be a FormGroup instance

and for nested:
<accordion *ngFor="let nestedItem of (parentItem | getArray:'NestedFormArray').controls; let i = index"> // get controls of the nested FormArray of the parent FormGroup

Or is there a better way to render such multi-level forms?

P.S. tested with console.log and the result of pipe is great: with methods or getters log is being called dozens of times per every click (every ChangeDetection with Default strategy), while pipe is being called only 1 time on the initial render.


Solution

  • The best option so far was to use pure pipes. Using this option, angular doesn't need to extract FormArrays from FormGroups every change detection cycle