Search code examples
angularangular7angular-reactive-formsformarrayformgroups

Angular 7 and form arrays error of cannot find a control with path


I am building a form group with an array inside of it, using mat-table data source.

I started by creating the table:

<form *ngIf="formGroup" [formGroup]="formGroup">
  <table mat-table [dataSource]="dataSource" *ngIf="total>0" formArrayName="distribution">
    <ng-container matColumnDef="ind_id">
      <th mat-header-cell *matHeaderCellDef> ID </th>
      <td mat-cell *matCellDef="let element">{{element.individual_id}}</td>

    </ng-container>
    <ng-container matColumnDef="ind_name">
      <th mat-header-cell *matHeaderCellDef> Name </th>
      <td mat-cell *matCellDef="let element">{{element.ind_name}}</td>

    </ng-container>
    <ng-container matColumnDef="ind_status">
      <th mat-header-cell *matHeaderCellDef> Ind. Status </th>
      <td mat-cell *matCellDef="let element">{{element.ind_status}}</td>

    </ng-container>
    <ng-container matColumnDef="project_kit">
      <th mat-header-cell *matHeaderCellDef> Kits </th>
      <td mat-cell *matCellDef="let element; let i=index;">
        <div [formGroupName]="i">
          <mat-form-field color="warn" appearance="fill">
            <mat-label>Kits</mat-label>
            <mat-select formControlName="kit" id="kit" placeholder="Kit">
              <mat-option *ngFor="let pk of projectKit" [value]="pk.project_kit">{{pk.kit_name}} part of project
                {{pk.project_name}}</mat-option>
            </mat-select>
          </mat-form-field>&nbsp;
        </div>
      </td>

    </ng-container>
    <ng-container matColumnDef="actual_date">
      <th mat-header-cell *matHeaderCellDef> Actual Date </th>
      <td mat-cell *matCellDef="let element; let i=index;">
        <div [formGroupName]="i">
          <mat-form-field color="warn" appearance="legacy">
            <mat-label>Actual Dist. Date</mat-label>
            <input formControlName="actual_date" matInput [matDatepicker]="picker2" placeholder="Actual Date">
            <mat-datepicker-toggle matSuffix [for]="picker2"></mat-datepicker-toggle>
            <mat-datepicker #picker2></mat-datepicker>
          </mat-form-field>
        </div>
      </td>
    </ng-container>
    <ng-container matColumnDef="note">
      <th mat-header-cell *matHeaderCellDef> Note </th>
      <td mat-cell *matCellDef="let element;let i=index;">
        <div [formGroupName]="i">
          <mat-form-field color="warn" appearance="legacy">
            <mat-label>Note</mat-label>
            <input formControlName="note" matInput type="text" placeholder="Note">
          </mat-form-field>
        </div>
      </td>
    </ng-container>
    <ng-container matColumnDef="actions">
      <th mat-header-cell *matHeaderCellDef> Actions </th>
      <td mat-cell *matCellDef="let element">

        <button mat-raised-button color="warn" type="submit" color="warn" (click)="addDist(element)">
          <mat-icon>add</mat-icon> Add
        </button>
      </td>
    </ng-container>

    <tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
    <tr mat-row *matRowDef="let row; columns: displayedColumns;"
      }"></tr>

  </table>
</form>

And for the typescript:

this.formGroup = this.fb.group({
      distribution: this.fb.array([
    this.createArray()
  ])
});

And createArray():

createArray(): FormGroup {
    return this.fb.group({
      'note': new FormControl(''),
      'kit': new FormControl(''),
      'actual_date': new FormControl('')
    });

The first typescript is fired when the user upload a file:

<form [formGroup]="uploadForm" role="form">
      <input #fileInput type="file" formControlName="upload" value="Upload Excel/CSV file"
        (change)="upload($event.target.files)" accept=".xlsx, .xls, .csv" />
      <button mat-raised-button id="inputFile" color="accent" (click)="reset()">
        <mat-icon color="warn">cancel</mat-icon>Reset
      </button>
    </form>

There is no error for the previous form of uploading a file.

But when I upload a file an error appears for the form array:

Error: Cannot find control with path: 'distribution -> 1' at _throwError (forms.js:1790) at setUpFormContainer (forms.js:1772) at FormGroupDirective.push

And it's pointing on:

<div [formGroupName]="i">
            <mat-form-field color="warn" appearance="fill">
              <mat-label>Kits</mat-label>
              <mat-select formControlName="kit" id="kit" placeholder="Kit">
                <mat-option *ngFor="let pk of projectKit" [value]="pk.project_kit">{{pk.kit_name}} part of project
                  {{pk.project_name}}</mat-option>
              </mat-select>
            </mat-form-field>&nbsp;
          </div>

EDIT

After Adding {{formGroup.value| JSON}} I got this:

{ "distribution": [ { "note": "", "kit": "", "actual_date": "" } ] }


Solution

  • the problem is your dataSource, let i=index make reference to the values of the dataSource, if you has less element in your array than in your dataSource your code crash

    If all your elements in the table belongs to the FormArray it's "easy", you can see an example in this stackblitz

    There are two keys,

    one how create the Form

    myform:FormGroup=new FormGroup({
        distribution:new FormArray(ELEMENT_DATA.map(x=>{
          return new FormGroup({
          position:new FormControl(x.position),
          name:new FormControl(x.name),
          weight:new FormControl(x.weight),
          symbol:new FormControl(x.symbol),
    
        })}))
      });
    

    And, how We refereed to the controls

    <form *ngIf="myform" [formGroup]="myform">
      <ng-container formArrayName="distribution">
    <!--see that datasource is myForm.get('distribution').controls-->
    <table mat-table [dataSource]="myform.get('distribution').controls" class="mat-elevation-z8" >
    <ng-container matColumnDef="position">
        <th mat-header-cell *matHeaderCellDef> No. </th>
        <!--so, "element" is a formGroup-->
        <td mat-cell *matCellDef="let element;let i=index" [formGroup]="element"> <input formControlName="position" > </td>
      </ng-container>
     ....
    </table>
    </ng-container>
    </form>
    

    But you has a "dataSource" and a formArray some disconected. You can create a function to referred to the array

    get distributionArray()
    {
       return this.myForm.get('distribution') as FormArray
    }
    

    And use in your td some like

    <td mat-cell *matCellDef="let element;let i=index" 
         [formGroup]="distributionArray.at(i)"> 
        <input formControlName="name" > 
    </td>
    

    }

    well, it's not necesary has value to all, but must there are so many elements in the array as in the dataSource, e.g.

    this.myform:FormGroup=new FormGroup({
            distribution:new FormArray(ELEMENT_DATA.map(()=>{
              //only two properties empty
              return new FormGroup({
              weight:new FormControl(),
              symbol:new FormControl(),
    
            })}))
          });
    

    Or using push

    this.myform:FormGroup=new FormGroup({
                distribution:new FormArray([])
    });
    ELEMENT_DATA.forEach(()=>{
        (this.myForm.get('distribution') as FormArray).push(this.createArray())
    }