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>
</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>
</div>
EDIT
After Adding {{formGroup.value| JSON}}
I got this:
{ "distribution": [ { "note": "", "kit": "", "actual_date": "" } ] }
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())
}