1

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": "" } ] }

alim1990
  • 4,656
  • 12
  • 67
  • 130

3 Answers3

2

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())
}
Eliseo
  • 50,109
  • 4
  • 29
  • 67
  • Sir, the control arrays are generated for each row in datasource and there is not data inside of them. Anyway I will give a try but with value empty for theb3 arrays – alim1990 Mar 20 '19 at 04:53
  • @alim1990, theb3 array can be all the controls value empty but must there are so many elements that the "dataSource", I updated my answer to show how do it – Eliseo Mar 20 '19 at 08:10
-1

Ok. so it means you're not adding enough formgroups in array. You are calling create only once. so only first one in array works. when the index goes to 1 from 0 there is no formgroup in formarray. As I understand you want to create formgroup for each row. so you need to call your function many times.

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

instead of creating array every time you should probably push new formgroup to existing formarray.

write a getter for distribution for easier use.

get distribution() {
   this.formGroup.get('distribution') as FormArray;
}    

then you can do

this.distribution.push(this.fb.group({
      'note': new FormControl(''),
      'kit': new FormControl(''),
      'actual_date': new FormControl('')
    }));

for each row. I'm not sure where you are doing create but this would go there for each row.

and keep the formGroup to i instead of i-1 or 0.

GreedyAi
  • 2,709
  • 4
  • 29
  • 55
-2

You have to define form array name above your [formGroup] start like this formArrayName="distribution"

joka00
  • 2,357
  • 1
  • 10
  • 27
Er Abdul Meman
  • 163
  • 1
  • 9