I get an array of objects from API that contain, among other data, a targets_and_rewards
array that can vary with a minimum of one occurence.
I created a material table showing these values, using static columns and dynamic columns based on the maximum length of targets_and_rewards
array for each offer.
I need to bind td
s correctly based on these dynamic columns.
Here is how it looks like:
app.component.ts
import { Component, OnInit } from '@angular/core';
import { MatTableDataSource } from '@angular/material/table';
const dataReceivedFromAPI = [
{
id: 4000,
name: 'My awesome offer',
targets_and_rewards: [
{
reward: {
max: 3.6,
mean: 0.48,
min: 0.4,
},
target: {
max: 30,
mean: 3.71,
min: 3,
},
},
{
reward: {
max: 5.7,
mean: 1.17,
min: 1,
},
target: {
max: 5.7,
mean: 5.88,
min: 5,
},
},
{
reward: {
max: 7.5,
mean: 3.38,
min: 3.3,
},
target: {
max: 30,
mean: 13.35,
min: 13,
},
},
],
},
{
id: 5000,
name: 'My awesome second offer',
targets_and_rewards: [
{
reward: {
max: 3.5,
mean: 0.55,
min: 0.3,
},
target: {
max: 35,
mean: 5.67,
min: 4,
},
},
{
reward: {
max: 6.8,
mean: 2.12,
min: 2,
},
target: {
max: 7.9,
mean: 4.12,
min: 3,
},
},
{
reward: {
max: 8.2,
mean: 5.24,
min: 4.87,
},
target: {
max: 32,
mean: 17.13,
min: 15.65,
},
},
{
reward: {
max: 9,
mean: 6.66,
min: 5.87,
},
target: {
max: 50,
mean: 34.45,
min: 21.12,
},
},
{
reward: {
max: 8.2,
mean: 5.24,
min: 4.87,
},
target: {
max: 32,
mean: 17.13,
min: 15.65,
},
},
],
},
{
id: 6000,
name: 'My awesome third offer',
targets_and_rewards: [
{
reward: {
max: 6.1,
mean: 5.54,
min: 4.13,
},
target: {
max: 100,
mean: 45.12,
min: 31.1,
},
},
],
},
];
interface Targeting {
id: number;
name: string;
targets_and_rewards: Array<{
reward: {
max: number;
mean: number;
min: number;
};
target: {
max: number;
mean: number;
min: number;
};
}>;
}
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
public dataSource: MatTableDataSource<Targeting>;
public displayedColumns: Array<string> = ['id', 'name'];
public targetAndRewardColumns: Array<string> = [];
ngOnInit(): void {
// API call binding data variable... (here `dataReceivedFromAPI`)
// Populate columns with static part (basic `displayedColumns`) and dynamic part (`targetAndRewardColumns`)
this.populateColumns(dataReceivedFromAPI);
this.dataSource = new MatTableDataSource(dataReceivedFromAPI);
}
populateColumns(data: Array<Targeting>): void {
let maxTargetsAndRewards: number = 0;
data.map((offer: Targeting) => {
const targetsAndRewardsLength: number = Object.keys(
offer.targets_and_rewards
).length;
if (targetsAndRewardsLength > maxTargetsAndRewards) {
maxTargetsAndRewards = targetsAndRewardsLength;
}
});
for (let i: number = 0; i < maxTargetsAndRewards; i++) {
this.targetAndRewardColumns.push(
`Min Target (${i})`,
`Min Reward (${i})`,
`Mean Target (${i})`,
`Mean Reward (${i})`,
`Max Target (${i})`,
`Max Reward (${i})`
);
}
this.displayedColumns = [
...this.displayedColumns,
...this.targetAndRewardColumns,
];
}
}
app.component.html
<table mat-table [dataSource]="dataSource">
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef>Id</th>
<td mat-cell *matCellDef="let offer">
{{ offer.id }}
</td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>Name</th>
<td mat-cell *matCellDef="let offer">
{{ offer.name }}
</td>
</ng-container>
<ng-container
*ngFor="let column of targetAndRewardColumns"
[matColumnDef]="column"
>
<th mat-header-cell *matHeaderCellDef>
{{ column }}
</th>
<!-- How to dynamically bind tds here ? -->
<!-- Min target (0) should be equal to first offer.targets_and_rewards[0].target.min for example -->
<td mat-cell *matCellDef="let offer">
<!-- Just to populate with something by default -->
{{ offer.targets_and_rewards[0].target.min }}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let offers; columns: displayedColumns"></tr>
</table>
And here is a stackblitz snippet to make everything more clear.