I have a mat-table
with form control components in each row like image below:
and the data model like below:
export class ProductClass {
constructor(
public id: string,
public name: string,
public date: Date,
public weight: number,
public input1: string,
public input2: string
) {}
}
Each row of data contains Product ID
, Product Name
, and Product Date
as header, and additional child data which are Weight
, Input1
, and Input 2
.
I populate the table with sample data below:
sample: ProductClass[] =
[
new ProductClass('PID1', 'Product x', new Date(), 1, 'a', '1'),
new ProductClass('PID2', 'Product g', new Date(), 2, 'b', '2'),
new ProductClass('PID3', 'Product s', new Date(), 3, 'c', '3'),
new ProductClass('PID4', 'Product j', new Date(), 4, 'a', '4'),
new ProductClass('PID5', 'Product r', new Date(), 5, 'b', '5'),
new ProductClass('PID6', 'Product w', new Date(), 6, 'c', '6'),
new ProductClass('PID7', 'Product k', new Date(), 7, 'a', '7'),
];
I am using mat-sort-header
to enable the table sorting, but I realize when perform the sort, all data with static text will sort accordingly, except the form control component which are input1
and input2
. For example, base on the sample data above, I have the input1
value as "a,b,c,a,b,c,a"
, so if I sort the table with Product ID
descending order, the input1
value suppose to be in order of "a,c,b,a,c,b,a"
, but it is not. After the sort, input1
value is in the order of "a,b,b,a,c,c,a"
, which just doesn't make sense. But if I put the value of input1
as static text like {{child.value.input1}}
, it will display the correct value
Template
<form [formGroup]="productForm">
<main>
<section class="container-fluid">
<table mat-table formArrayName="productsArray" [dataSource]="tableDetails" multiTemplateDataRows
class="maTable maTable-bordered" matSort>
<ng-container matColumnDef="id">
<mat-header-cell *matHeaderCellDef mat-sort-header> Product ID </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.value.id}} </mat-cell>
</ng-container>
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef mat-sort-header> Product Name </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.value.name}} </mat-cell>
</ng-container>
<ng-container matColumnDef="date">
<mat-header-cell *matHeaderCellDef mat-sort-header> Product Date </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.value.date | date:'dd-MMM-yyyy'}} </mat-cell>
</ng-container>
<ng-container matColumnDef="expandedDetail">
<mat-cell *matCellDef="let child ; let rowindex = dataIndex" [attr.colspan]="tableColumns.length"
[formGroupName]="getActualIndex(rowindex)">
<div class="col-sm-6 mt-3">
<div class="row">
<h6>Weight: </h6>
<p class="rounded-sm"> {{child.value.weight}}KG </p>
</div>
<div class="row">
<h6>Input 1</h6><br/>
<input type="radio" formControlName="input1" value="a">
<label for="a">A</label>
<input type="radio" formControlName="input1" value="b">
<label for="b">B</label>
<input type="radio" formControlName="input1" value="c">
<label for="c">C</label>
</div>
{{child.value.input1}}
<div class="row pb-1">
<h6>Input 2</h6>
<textarea formControlName="input2" class="rounded-sm border-light-gray px-4" aria-label="Remark" maxlength="500"></textarea>
{{child.value.input2}}
</div>
</div>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="tableColumns"></mat-header-row>
<mat-row matRipple *matRowDef="let child; columns: tableColumns;" class="element-row"></mat-row>
<mat-row *matRowDef="let row ; columns: ['expandedDetail'];" style="overflow: hidden"></mat-row>
</table>
<mat-paginator [pageSizeOptions]="[5, 10, 25, 100]"></mat-paginator>
</section>
</main>
</form>
Component
import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core';
import { FormBuilder, FormGroup, AbstractControl } from '@angular/forms';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort, Sort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, AfterViewInit {
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
search: string = '';
tableColumns: string[] = ['id', 'name', 'date'];
tableDetails: MatTableDataSource<AbstractControl>;
productArray = this.fb.array([]);
productForm = this.fb.group({
productsArray: this.productArray
});
sample: ProductClass[] = [
new ProductClass('PID1', 'Product x', new Date(), 1, 'a', '1'),
new ProductClass('PID2', 'Product g', new Date(), 2, 'b', '2'),
new ProductClass('PID3', 'Product s', new Date(), 3, 'c', '3'),
new ProductClass('PID4', 'Product j', new Date(), 4, 'a', '4'),
new ProductClass('PID5', 'Product r', new Date(), 5, 'b', '5'),
new ProductClass('PID6', 'Product w', new Date(), 6, 'c', '6'),
new ProductClass('PID7', 'Product k', new Date(), 7, 'a', '7')
];
constructor(private fb: FormBuilder) {}
ngOnInit(): void {
for (const product of this.sample) {
this.productArray.push(
this.fb.group({
id: product.id,
name: product.name,
date: product.date,
weight: product.weight,
input1: product.input1,
input2: product.input2
})
);
}
}
protected init(): void {
this.tableDetails = new MatTableDataSource();
this.tableDetails.data = this.productArray.controls;
this.tableDetails.paginator = this.paginator;
this.tableDetails.sort = this.sort;
this.tableDetails.sortingDataAccessor = (
item: AbstractControl,
property
) => {
switch (property) {
case 'date':
return new Date(item.value.date);
default:
return item.value[property];
}
};
const filterPredicate = this.tableDetails.filterPredicate;
this.tableDetails.filterPredicate = (data: AbstractControl, filter) => {
return filterPredicate.call(this.tableDetails, data.value, filter);
};
}
ngAfterViewInit(): void {
this.init();
}
applyFilter(): void {
this.tableDetails.filter = this.search.trim().toLowerCase();
}
getActualIndex(index: number): number {
return index + this.paginator.pageSize * this.paginator.pageIndex;
}
}
export class ProductClass {
constructor(
public id: string,
public name: string,
public date: Date,
public weight: number,
public input1: string,
public input2: string
) {}
}
I tried to duplicate my code on Stackbitz, although CSS doesn't work, but it did replicate the issue I described above.
If I change the pagination size to be larger than the amount of data being displayed, so that all the rows can be rendered on a single page, then the problem does not occur.
Any help would be greatly appreciated!
Turn out it is the problem with FormGroupName
, you can't set your FormGroupName
with the dataIndex
as when you sort the table with mat-sort-header
, it only sort the table on UI layer, didn't change the actual order of data set (the order of tableDetails.data
)
So instead of [formGroupName]="getActualIndex(rowindex)"
, I change it to [formGroupName]="tableDetails.data.indexOf(child)"
.