angularangular-reactive-formsformarray

HTML iteration of nested formArrays


Is it posible to have a formArray nested inside of other formArray and show it properly in the HTML view?

I'm building a paginated form based on an API response, so I'm iterating over my response to create this form, so I would end up having something like:

this.form = this.formBuilder.group({ pages : this.formBuilder.array([ this.formBuilder.array([]) ]) });

So my form looks like:

-> this.form = FormGroup (controls)
   -> pages = FormArray (controls)
      -> 0 = FormArray (controls)
        -> 0 = FormGroup (controls)
           -> Name (my form fields)
           -> Active (my form fields)

So iterate through it in the DOM is quite weird to me and I was wondering if there's a better way to achieve this or if this is the proper way indeed

 <form [formGroup]="form" (ngSubmit)="onSubmit()">
     <ng-container formArrayName="pages">
       <ng-container *ngFor="let page of pages.controls; let i = index">
         <div *ngIf="i === paginationIndex" [formGroupName]="i">
           <div *ngFor="let myControl of page['controls']; let i = index" [formGroupName]="i">
             {{myControl.get('name')?.value }}
             <input type="text" formControlName="active">
           </div>
         </div>
       </ng-container>
     </ng-container>
     <div>
       <button class="btn" type="submit">Submit</button>
     </div>
</form>

Solution

  • Yes, we can. We should create two formArrayName one for pages and another with the nested form array name index. Apart from this, I used some functions to set the type of the elements in HTML, which were not inferred, please refer the below working example.

    I don't think it's a good idea to use formArrayName, formGroupName etc. on ng-container elements. Instead go for a plain DIV, because the elements don't get generated in HTML.

    When working with forms and you want to hide some fields, always go for [hidden] to hide that element, destroying the element might give you problems:

    import { CommonModule } from '@angular/common';
    import { Component, inject } from '@angular/core';
    import {
      ReactiveFormsModule,
      FormGroup,
      FormBuilder,
      FormsModule,
      FormArray,
    } from '@angular/forms';
    import { bootstrapApplication } from '@angular/platform-browser';
    import 'zone.js';
    
    @Component({
      selector: 'app-root',
      standalone: true,
      imports: [FormsModule, ReactiveFormsModule, CommonModule],
      template: `
        <form [formGroup]="form" (ngSubmit)="onSubmit()">
            <div formArrayName="pages">
              <div *ngFor="let page of pages.controls; let i = index" [formArrayName]="i">
                <div [hidden]="i !== paginationIndex">
                  <div *ngFor="let myControl of setType(page).controls; let i = index" [formGroupName]="i">
                    {{myControl.get('name')?.value }}
                    <input type="text" formControlName="active">
                  </div>
                </div>
              </div>
            </div>
            <div>
              <button class="btn" type="submit">Submit</button>
            </div>
        </form>
      `,
    })
    export class App {
      paginationIndex = 0;
      name = 'Angular';
      formBuilder = inject(FormBuilder);
      form: FormGroup = new FormGroup({});
    
      get pages() {
        return this.form.get('pages') as FormArray;
      }
    
      setType(page: any) {
        return page as FormArray;
      }
    
      ngOnInit() {
        this.form = this.formBuilder.group({
          pages: this.formBuilder.array([
            this.formBuilder.array([
              this.formBuilder.group({
                active: true,
              }),
              this.formBuilder.group({
                active: false,
              }),
            ]),
            this.formBuilder.array([
              this.formBuilder.group({
                active: true,
              }),
              this.formBuilder.group({
                active: false,
              }),
            ]),
          ]),
        });
      }
    
      onSubmit() {}
    }
    
    bootstrapApplication(App);
    

    Stackblitz Demo