Search code examples
angularangular-reactive-formsformbuilder

Angular 8 Nested FormBuilder Can't find control


I'm trying to iterate over values in FormBuilder in Angular 8 The path is FormGroup -> FormArray -> FormGroup -> FormArray But i got this error: Cannot find control with path: 'conditions -> 2 -> values -> 0 -> [object Object]' I tried lot of combinaison but no one works

In my component:

conditionsForm: FormGroup;
  conditionForm: FormArray;
  conditionValuesForm: FormArray;

  ngOnInit() {
    this.conditionsForm = this.formBuilder.group({
      conditions: this.formBuilder.array([this.createConditionForm()])
    });
  }

  createConditionForm(): FormGroup {
    return this.formBuilder.group({
      type: "",
      operator: "",
      values: this.formBuilder.array([this.createConditionValuesForm()])
    });
  }

  createConditionValuesForm(): FormGroup {
    return this.formBuilder.group({
      value: ""
    });
  }

  addConditionForm(): void {
    this.conditionForm = this.conditionsForm.get("conditions") as FormArray;
    this.conditionForm.push(this.createConditionForm());
  }

  addConditionValuesForm(): void {
    this.conditionValuesForm = this.conditionForm.get("values") as FormArray;
    this.conditionValuesForm.push(this.createConditionValuesForm());
  }

In my template:

<form [formGroup]="conditionsForm" class="row container-condition">
    <div formArrayName="conditions" *ngFor="let condition of conditionsForm.get('conditions').controls; let i_condition = index">
        <div class="row row-condition" [formGroupName]="i_condition">
            <!-- INPUT VALUE -->
            <div [formGroup]='condition'></div>
            <div formArrayName="values" *ngFor="let value of condition.get('values').controls; let i_value = index">
                <div [formGroupName]="i_value">
                    <mat-form-field class="example-full-width">
                        <input matInput placeholder="Value" value="" [formControlName]="value.value">
                    </mat-form-field>
                </div>
            </div>
        </div>
    </div>
    <button (click)="addConditionForm()">Add condition</button>
    <button type="button" (click)="printMyForm()">Print to console</button>
</form>

Solution

  • slight changes to html:

    <form [formGroup]="conditionsForm">
        <div
         formArrayName="conditions"
         *ngFor="let condition of conditionsForm.get('conditions').controls; let i_condition = index">
            <div [formGroupName]="i_condition"> <!-- correctly binding dynamic index variable -->
                <!-- also note this: 'condition' is not a valid group name within the group you created for the 'conditions' array. --> 
                <!-- <div [formGroup]='condition'></div> --> 
    
              <input type="text" formControlName="type" placeholder="type"> <!-- hardcoded 'type' control name, as it is steady with all groups within 'condition' -->
              <input type="text" formControlName="operator" placeholder="operator">
                <div
                 formArrayName="values"
                 *ngFor="let value of condition.get('values').controls; let i_value = index">
                    <div [formGroupName]="i_value"> <!-- correctly binding dynamic index -->
     <!-- instead of [formControlName]="value.value", which would approach to your ts file and look for a control in an object like this: value = {value: new FormControl()...} -->
                      <input placeholder="Value" value="" formControlName="value">
                    </div>
                </div>
            </div>
        </div>
    </form>
    

    the main problem was a concurrent confusement with property binding vs literal binding

    note that immediate children of formArray are named 0, 1, 2, 3..., while groups have an explicit name only if you explicitly name them, e.g

    this.myForm = this.formBuilder.group({
      myInput: [null],
      myGroup: this.formBuilder.group({...}),
      myArray: this.formBuilder.array([])
    })
    

    and in html

    <!-- property binding -->
    <form [formGroup]="myForm"/>
       <!-- literal binding because parent is declared and known (myForm) -->
       <input formControlName="myInput"/>
       <!-- literal binding because parent is declared and known (myForm) -->
       <div formGroupName="myGroup">
       <!-- literal binding because parent is declared and known (myGroup) -->
          <input formControlName="nestedInput"/>
       </div>
       <!-- literal binding because parent is declared and known (myForm) -->
       <div formArrayName="myArray" *ngFor="let control of myForm.get('myArray').controls; let i_control = index">
       <!-- property binding because items in array are accessed by index, and not by name, and is generated on the fly, nowhere declared in your code (and shouldn't be) -->
          <input [formControlName]="i_control"/>
          <!-- or -->
          <div [formGroupName]="i_control">
            <!-- literal binding because parent is known by name (i_control) -->
            <input formControlName="myInputInOneOfTheGroupsOfMyArray"/>
          </div>
       </div>
    </form>
    

    stackblitz: