Check out this fully functional solution along with a Stackblitz Demo demonstrating cell merging capabilities.
To achieve the desired effect, you need to calculate a row span
for each item and assign it to the rowspan
attribute of the respective td
. Additionally, conditionally render the td
to only display it for the first occurrence of each state.
An effective strategy involves preprocessing an array sorted by state and county, incorporating added span
properties for both states and counties.
Determining these span
properties involves tallying the child count for each state and county within that state by filtering the original data array.
The objective is to structure the array as follows:
[
{state: "CA", county: "2", item: 0.019, stateSpan: 3, countySpan: 2},
{state: "CA", county: "2", item: 0.037, stateSpan: 0, countySpan: 0},
{state: "CA", county: "3", item: 0.14, stateSpan: 0, countySpan: 1},
{state: "MN", county: "1", item: 0.297, stateSpan: 4, countySpan: 3},
{state: "MN", county: "1", item: 0.04, stateSpan: 0, countySpan: 0},
{state: "MN", county: "1", item: 0.0374, stateSpan: 0, countySpan: 0},
{state: "MN", county: "3", item: 0.14, stateSpan: 0, countySpan: 1}
]
Referencing the code snippet below:
<table>
<tr>
<th>State</th>
<th>County</th>
<th>Item</th>
</tr>
<tr *ngFor="let item of dataExt">
<td [attr.rowspan]="item.stateSpan" *ngIf="item.stateSpan">{{ item.state }}</td>
<td [attr.rowspan]="item.countySpan" *ngIf="item.countySpan">{{ item.county }}</td>
<td>{{ item.item }}</td>
</tr>
</table>
export class AppComponent {
data = [
{ state: 'MN', county: '1', item: 0.297 },
{ state: 'MN', county: '1', item: 0.04 },
{ state: 'MN', county: '3', item: 0.14 },
{ state: 'CA', county: '2', item: 0.019 },
{ state: 'MN', county: '1', item: 0.0374 },
{ state: 'CA', county: '2', item: 0.037 },
{ state: 'CA', county: '3', item: 0.14 }
];
dataExt: any[] = [];
constructor() {
this.processData();
}
private processData() {
const statesSeen = {};
const countiesSeen = {};
this.dataExt = this.data.sort((a, b) => {
const stateComp = a.state.localeCompare(b.state);
return stateComp ? stateComp : a.county.localeCompare(b.county);
}).map(x => {
const stateSpan = statesSeen[x.state] ? 0 :
this.data.filter(y => y.state === x.state).length;
statesSeen[x.state] = true;
const countySpan = countiesSeen[x.state] && countiesSeen[x.state][x.county] ? 0 :
this.data.filter(y => y.state === x.state && y.county === x.county).length;
countiesSeen[x.state] = countiesSeen[x.state] || {};
countiesSeen[x.state][x.county] = true;
return { ...x, stateSpan, countySpan };
});
}
}
The resulting table can be viewed here:
https://i.stack.imgur.com/oq3va.png