Search code examples
angulartypescriptangular-forms

Angular FormGroup inside FormArray using new control flow


I have a reactive angular form that has a formArray of formGroups called sections:

  sectionForm = new FormGroup({
    title: new FormControl<string>('New Section', {nonNullable: true, validators: [Validators.required]}),
    subTitle: new FormControl<string>('section sub title', {nonNullable: true}),
    description: new FormControl<string>('description', {nonNullable: true}),
  });

  sessionForm = new FormGroup({
    title: new FormControl<string>('', {nonNullable: true, validators: [Validators.required]}),
    subTitle: new FormControl<string>('', {nonNullable: true, validators: [Validators.required]}),
    description: new FormControl<string>('', {nonNullable: true, validators: [Validators.required]}),
    sections: new FormArray([this.sectionForm])
  });

I am using reactive forms here and I want to build the html form to match this set up. I have the following section of my form that attempts to use the new control flow to render the sections form group inside the form array:

    <form [formGroup]="sessionForm" class="k-form k-form-md">
    <fieldset class="k-form-fieldset">

     <!--  other form controls ........ -->
      <div cy-data="session-sections">
        @for (section of sessionForm.controls.sections.controls; track $index) {
          <div forGroupName="section">
            <kendo-formfield cy-data="section-title">
              <kendo-label [for]="sectiontitle" text="Section Title"></kendo-label>
              <kendo-textbox
                #sectiontitle
                class="text-input"
                formControlName="title"
                [clearButton]="true"
                required
              ></kendo-textbox>
              <kendo-formerror>Error:Section Title Required</kendo-formerror>
            </kendo-formfield>
            <kendo-formfield cy-data="section-subtitle">
              <kendo-label [for]="sectionsubtitle" text="Section Sub Title"></kendo-label>
              <kendo-textbox
                #sectionsubtitle
                class="text-input"
                formControlName="subTitle"
                [clearButton]="true"
                required
              ></kendo-textbox>
              <kendo-formerror>Error:Section Sub Title Required</kendo-formerror>
            </kendo-formfield>
            <kendo-formfield cy-data="section-description">
              <kendo-label [for]="sectiondescription" text="Section Description"></kendo-label>
              <kendo-textarea
                #sectiondescription
                class="text-input"
                formControlName="description"
                required
              ></kendo-textarea>
              <kendo-formerror>Error:Section Description Required</kendo-formerror>
            </kendo-formfield>
          </div>
        }
      </div>

    </fieldset>
    </form>

THE PROBLEM:

the inputs listed above that share a name with the parent form group are bound to the controls in the parent form group and not the controls inside the form array. So whatever I enter in the sectionTitle input updates the parent title property.

I thought that setting the formGroupName in the div to section <div forGroupName="section"> would change the context for the controls inside of it. It does not.

Can someone help me figure out how to bind the controls inside the form array considering that there will be multiple "section" form groups inside that array that need to render its respective controls


Solution

  • You need to define the formArrayName="sections" above the for loop, so that it's clear the controls belong to the form array.

    Then on the first element inside the for loop, we need to set the [formGroupName]="index" so that it's clear that the block of code belongs to the nth element of the form array's formGroup:

    ...
    <div cy-data="session-sections" formArrayName="sections">
            @for (section of sessionForm.controls.sections.controls;let index = $index; track index) {
              <div [formGroupName]="index">
              ...
    

    Full code:

    import { Component, ViewChild, ViewEncapsulation } from '@angular/core';
    import { FormGroup, FormControl } from '@angular/forms';
    import { TextBoxComponent } from '@progress/kendo-angular-inputs';
    import { FormArray, ReactiveFormsModule, Validators } from '@angular/forms';
    
    @Component({
      selector: 'my-app',
      template: `
      <pre>{{sessionForm.value | json}}</pre>
            <form [formGroup]="sessionForm" class="k-form k-form-md">
        <fieldset class="k-form-fieldset">
    
         <!--  other form controls ........ -->
          <div cy-data="session-sections" formArrayName="sections">
            @for (section of sessionForm.controls.sections.controls;let index = $index; track index) {
              <div [formGroupName]="index">
                <kendo-formfield cy-data="section-title">
                  <kendo-label [for]="sectiontitle" text="Section Title"></kendo-label>
                  <kendo-textbox
                    #sectiontitle
                    class="text-input"
                    formControlName="title"
                    [clearButton]="true"
                    required
                  ></kendo-textbox>
                  <kendo-formerror>Error:Section Title Required</kendo-formerror>
                </kendo-formfield>
                <kendo-formfield cy-data="section-subtitle">
                  <kendo-label [for]="sectionsubtitle" text="Section Sub Title"></kendo-label>
                  <kendo-textbox
                    #sectionsubtitle
                    class="text-input"
                    formControlName="subTitle"
                    [clearButton]="true"
                    required
                  ></kendo-textbox>
                  <kendo-formerror>Error:Section Sub Title Required</kendo-formerror>
                </kendo-formfield>
                <kendo-formfield cy-data="section-description">
                  <kendo-label [for]="sectiondescription" text="Section Description"></kendo-label>
                  <kendo-textarea
                    #sectiondescription
                    class="text-input"
                    formControlName="description"
                    required
                  ></kendo-textarea>
                  <kendo-formerror>Error:Section Description Required</kendo-formerror>
                </kendo-formfield>
              </div>
            }
          </div>
    
        </fieldset>
        </form>
        `,
      encapsulation: ViewEncapsulation.None,
      styleUrls: ['./styles.css'],
    })
    export class AppComponent {
      sectionForm = new FormGroup({
        title: new FormControl<string>('New Section', {
          nonNullable: true,
          validators: [Validators.required],
        }),
        subTitle: new FormControl<string>('section sub title', {
          nonNullable: true,
        }),
        description: new FormControl<string>('description', { nonNullable: true }),
      });
    
      sessionForm = new FormGroup({
        title: new FormControl<string>('', {
          nonNullable: true,
          validators: [Validators.required],
        }),
        subTitle: new FormControl<string>('', {
          nonNullable: true,
          validators: [Validators.required],
        }),
        description: new FormControl<string>('', {
          nonNullable: true,
          validators: [Validators.required],
        }),
        sections: new FormArray([this.sectionForm]),
      });
    }
    

    Stackblitz Demo