Reputation: 548
I am trying to figure out how to get a grouped and aggregated array of values (that I can use with ngFor) starting from a list of objects, and for the life of me I can't make it work. The data (which is a slice of my state) looks something like this:
[{name: "A", value: 1, desc: 'something irrelevant'},
{name: "A", value: 3, desc: 'also other properties'},
{name: "B", value: 2, desc: 'etc.'},
{name: "B", value: 5, desc: 'etc.'}]
And the result I'm trying to get is something like (note that the type is different):
[{name: "A", value: 4}, {name: "B", value: 7}]
So, basically I want to find the distinct "names" and the sum of "value" for all the objects that have that name, with an output that can be used by ngFor | async.
My almost working solution to get the distinct values, at the moment, is:
this.aggregates:Observable<any[]> = this.store
.select(state => state.valuesList)
.map(valuesList => valuesList.sort((a,b) => {return (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0);} }))
.flatMap(valuesList => valuesList)
.map(value => value.name)
.distinct();
I'd be happy to start with this; the problem is that, if I don't add a toArray(), Typescript complains about "Type string is not assignable to type any[]"; if I add toArray() after distinct(), it doesn't complain anymore but a subscribe() yields no result.
What am I doing wrong? Should I move everything to the reducer (but then I don't know if I can change the type of objects returned by different actions in the same reducer)? Any help is very very much appreciated.
UPDATE: I'd be even happier to have a working groupBy() implementation, since it should be exactly its use case.
Upvotes: 2
Views: 3338
Reputation: 58430
You can use groupBy
to do what you want, but you have to use the groupBy
operator on an observable derived from the list - as groupBy
expects an observable of items.
In the following snippet, slice
is equivalent to this.store.select(state => state.valuesList)
const slice = Rx.Observable.of([
{ name: "A", value: 1, desc: "something irrelevant" },
{ name: "A", value: 3, desc: "also other properties" },
{ name: "B", value: 2, desc: "etc." },
{ name: "B", value: 5, desc: "etc." }
]);
const grouped = slice.concatMap(list => Rx.Observable
.from(list)
.groupBy(item => item.name)
.mergeMap(group => group
.reduce((total, item) => total + item.value, 0)
.map(total => ({ name: group.key, value: total }))
)
.toArray()
);
grouped.subscribe(value => console.log(value));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://unpkg.com/rxjs@5/bundles/Rx.min.js"></script>
Upvotes: 3
Reputation: 503
Somethings like this:
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/from';
import 'rxjs/add/operator/map';
function main() {
const t = [
{ name: "C", value: 3, desc: 'also other properties' },
{ name: "A", value: 1, desc: 'something irrelevant' },
{ name: "B", value: 2, desc: 'etc.' },
{ name: "A", value: 3, desc: 'also other properties' },
{ name: "B", value: 5, desc: 'etc.' }
];
const store = Observable.from(Array(100).fill(t));
const aggregates: Observable<any[]> = store
.map((valuesList) => valuesList
.map((x) => ({ name: x.name, value: x.value }))
.sort((a, b) => a.name.localeCompare(b.name))
.reduce((pre, cur) => {
const len = pre.length - 1;
if (pre[len] && pre[len].name === cur.name) {
pre[len].value += cur.value;
return pre;
}
pre[len + 1] = cur;
return pre;
}, [])
);
return aggregates;
}
main().subscribe((x) => {
console.dir(x, { depth: null });
});
The output is:
[ { name: 'A', value: 4 },
{ name: 'B', value: 7 },
{ name: 'C', value: 3 } ]
[ { name: 'A', value: 4 },
{ name: 'B', value: 7 },
{ name: 'C', value: 3 } ]
[ { name: 'A', value: 4 },....
Upvotes: 1