Search code examples
angulartypescriptangular-reactive-formsangular-formsformarray

Angular Reactive Form - How to target form control nested in array and object


I am fairly new to Angular and am trying to build a fairly complex form for a food delivery system I have been building.

I am currently building a form that would enable me to add a menu item to a restaurant's menu.

I have built an Angular form to do this, part of the form includes a section for addons, which would relate to things like sauces, flavors and etc. The data is structured in such a way that there is an addons array that contains addon objects containing an addon type as well as an array of options relating to that type.

I have built a method to create a new addon in my addons array with two inputs, one for the name being a standard control taking a string and the other being an array nested in this object that includes all the addon options relating to that type.

I am able to add a new addon as well as addon options within that addon object but when trying to update values of those nested options I am unable to attach a formControlName to each of those options which I need in order to update their values.

I keep getting the below error for reference:

vendor.js:63956 ERROR Error: Cannot find control with path: 'addons -> 0 -> 0 -> option

when creating the new option I'm not sure how to give it a unique formcontrol name so that its value can be updated. Any input would be massively appreciated.

Below is my code

Main Form:

menuForm = this.builder.group({
  name: this.builder.control<string>('', Validators.required),
  price: this.builder.control<string>('', Validators.required),
  description: this.builder.control<string>('', Validators.required),
  itemType: this.builder.control<string>('', Validators.required),
  image: this.builder.control<NonNullable<any>>('', Validators.required),
  imageName: this.builder.control<string>('', Validators.required),
  categories: this.builder.array([]),
  relatedsides: this.builder.array([]),
  addons: this.builder.array([]),
});

Methods for adding new addon and adding a new addon option:

addAddon() {
  const addOnForm = this.builder.group({
    addonname: ['', Validators.required],
    addonoptions: this.builder.array([]),
  });
    
  this.addons.push(addOnForm);
}
    
addAddonOption(i: number) {
  const addOnOptionForm = this.builder.group({
    option: this.builder.control<string>(''),
  });
    
  this.addons.value[i].addonoptions.push(addOnOptionForm);
    
  console.log('addons with options', this.addons.value);
}

HTML:

 <!-- Addons Array -->

      <ng-container type="form" formArrayName="addons">
        <h4>Add Addons</h4>
        <ng-container *ngFor="let addOnForm of addons.controls; let x = index">
          <div [formGroupName]="x" class="addons-form-row">
            <mat-form-field appearance="fill">
              <input matInput placeholder="Addon" formControlName="addonname" />
            </mat-form-field>

            <button type="button" (click)="deleteAddOn(x)">Delete Addon</button>
            <button type="button" (click)="addAddonOption(x)">
              add addon option
            </button>

            <ng-container
              *ngFor="
                let addonoption of addons.value[x].addonoptions;
                let k = index
              "
            >
              <div [formGroupName]="k" class="imbeded-addon-options">
                <mat-form-field appearance="fill">
                  <input
                    matInput
                    type="text"
                    placeholder="Add On Option"
                    formControlName="option"
                  />
                </mat-form-field>
              </div>
            </ng-container>
          </div>
        </ng-container>
      </ng-container>
      <div>
        <button type="button" (click)="addAddon()">Add Addon</button>
      </div>

Solution

  • Your structure of FormGroup from the root form to option control should be:

    Root FormGroup

    --> addons FormArray

    --> index of addon FormGroup

    --> addonoptions FormArray

    --> index of addonoption FormGroup

    --> option FormControl


    1. Miss out formArrayName="addonoptions" for the template.

    2. Modify the way to iterate the addonoptions FormArray from addon FormGroup.

    <ng-container formArrayName="addonoptions">
      <ng-container
        *ngFor="
          let addonoption of addonoptions(x).controls;
          let k = index
        "
      >
        <div [formGroupName]="k" class="imbeded-addon-options">
          <mat-form-field appearance="fill">
            <input
              matInput
              type="text"
              placeholder="Add On Option"
              formControlName="option"
            />
          </mat-form-field>
        </div> 
      </ng-container>
    </ng-container>
    
    addAddonOption(i: number) {
      const addOnOptionForm = this.builder.group({
        option: this.builder.control<string>(''),
      });
    
      this.addonoptions(i).push(addOnOptionForm);
    
      console.log('addons with options', this.addons.value);
    }
    
    addonoptions(addonFormIndex: number): FormArray {
      return (this.addons.get(`${addonFormIndex}`) as FormGroup).controls
        .addonoptions as FormArray;
    }
    

    Demo @ StackBlitz