Reputation: 443
Right now i have data coming in like this in my angular application
https://stackblitz.com/edit/angular-ymmt4d
How can i group and order the data by State and County and display like below table
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
data = [
{ state: 'MN', county: '1', item: 0.297 },
{ state: 'MN', county: '1', item: 0.04 },
{ state: 'CA', county: '2', item: 0.019 },
{ state: 'MN', county: '1', item: 0.0374 },
{ state: 'CA', county: '2', item: 0.037 }
]
}
<table >
<tr>
<th>State</th>
<th>County</th>
<th>Item</th>
</tr>
<ng-container *ngFor="let dataitem of data">
<tr>
<td>{{dataitem.state}}</td>
<td>{{dataitem.county}}</td>
<td>{{dataitem.item}}</td>
</tr>
</ng-container>
</table>
Upvotes: 4
Views: 13659
Reputation: 13964
Here is a fully working solution with a Stackblitz Demo with merging of similar cells.
You have to calculate a row span
for each item and bind it to the rowspan
attribute of the td
. You also have to conditionally render the td
to display it only for the first item for each state.
For this, you can create a preprocessed array, ordered by state and by county, with added span
properties for states and counties.
To set the span
properties, you can count the number of children for each state and each county for the state by filtering the original array.
The goal is to get a array like this:
[
{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}
]
Here is the code:
<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 };
});
}
}
Here is the resulting table:
Upvotes: 1
Reputation: 491
Perhaps something like this:
interface Item {
state?: string;
county?: string;
item: number;
}
private processData(data: any[]): Item[] {
// Sort alphabetically
const sorted = data.sort((a, b) => {
if(a.state < b.state) { return -1; }
if(a.state > b.state) { return 1; }
return 0;
});
// Return original object if state value is different to
// previous object (or if first iteration). Otherwise
// return object with sole `item` property.
return sorted.map((el, i) => {
if (i === 0 || sorted[i-1].state !== el.state) {
return el;
} else {
return {
item: el.item
};
}
});
}
That should be O(log n)
complexity and the result will look like this:
[
{ state: 'CA', county: '2', item: 0.019 },
{ item: 0.037 },
{ state: 'MN', county: '1', item: 0.297 },
...
]
Upvotes: 0
Reputation: 57929
if you has the data ordered you can use let i=index and check if the before data is eauql using a conditional operator
<ng-container *ngFor="let dataitem of data;let i=index">
<tr>
<td>{{i>0 && data[i-1].state==dataitem.state?'':dataitem.state}}</td>
<td>{{i>0 && data[i-1].county==dataitem.county?'':dataitem.county}}</td>
<td>{{dataitem.item}}</td>
</tr>
</ng-container>
Upvotes: 3
Reputation: 7672
transform your data into multiple arrays using lodash.
https://lodash.com/docs/4.17.11#groupBy
import { groupBy } from 'lodash-es'
const states = groupBy(data, 'state')
in your table you can have multiple tbodies
<tbody *ngFor="let state of states">...</tbody>
Upvotes: 2
Reputation:
Using this answer as a guide, you can groom your data ahead of time (or use a pipe) to create nested objects.
Something along the lines of (pulled roughly from the linked example)
key = 'state';
data = data.reduce((data, x) => {
(data[x[key]] = data[x[key]] || []).push(x);
return data;
}, {});
should produce something similar to
data = [
{ 'MN': [
{ county: '1', item: 0.297 }
....
]}
]
Alternatively you can use fancy *ngIf and index logic if your list is sorted. Read more here. Something along the lines of:
<div *ngFor="let item of list; let i = index;">
<div *ngIf="i > 0 && item.state !== list[i-1].state">{{item.state}}</div>
</div>
Upvotes: 1