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.