Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Counting items in GroupBy by group.key in Angular with rxjs

I have a very large array of items where there is a "Status" field that could have one of six statuses. Using the rxjs library, I am trying to .groupBy then .reduce to return an array of the six statuses and the count of items with those statuses.

Here is the method in my service:

getStatusTotals() {
        return Observable.from(sampleData)
            .map(res => res.d.results)
            .concatMap(data => data)
            .groupBy(item => item.Status)
            .mergeMap(group => group
                .reduce((total, item) => total, 0)
                .map(total => ({ Status: group.key, Count: total }))
                )
            .toArray()

    }

In the component where I am calling this service function, I set the value to statusData and the information is being presented as follows:

<div *ngFor="let t of tracdata | async" class="row">Status: {{t.Status}}; Count: {{t.Count}}</div>

This, however is giving me the following in my app:

Status: 5 - Dormant; Count: 0
Status: 2 - Active; Count: 0
Status: 4 - Assigned to Section; Count: 0
Status: 1 - Pending Review; Count: 0
Status: 3 - Officer Review; Count: 0

So I am doing something wrong. I have tried .count and .forEach, and read through the rxjs documentation, but I must be doing something wrong. Please help!

like image 534
kittycatbytes Avatar asked Nov 28 '25 22:11

kittycatbytes


2 Answers

Your reduce is missing +1 for actually counting the statuses

.reduce((total, item) => total + 1, 0)

Anyway, you can replace the .reduce(..) with .count()

like image 75
ZahiC Avatar answered Nov 30 '25 19:11

ZahiC


As @ZahiC said, reduce will work.

But reduce will only emit once you've got all the data.

If you want to compute all of that every time there's a next, you should use scan instead.

Here's an example :

const { Observable } = Rx;

const sampleData = [
  { status: 'status1' },
  { status: 'status2' },
  { status: 'status1' },
  { status: 'status3' },
  { status: 'status4' },
  { status: 'status2' },
  { status: 'status5' },
  { status: 'status6' },
  { status: 'status4' }
];

Observable
  .from(sampleData)
  .scan((acc, curr) => {
    const status = curr.status;
    const count = acc[status] ? acc[status].count : 0;

    return Object.assign(acc, {
      [status]: { status, count: count + 1 }
    });
  }, {})
  .do(console.log)
  .subscribe();

And a working Plunkr: https://plnkr.co/edit/U2mZC5QpL12NpuHTJOFq?p=preview

EDIT:

As this function gives an object and you want an array, I've added a small function for that :

const objToArray = (obj) => {
  const keys = Object.keys(obj);
  return keys.reduce((acc, curr) => [...acc, obj[curr]], []);
} 

Then I just map the Observable to it before the do :

...
.map(objToArray)
.do(console.log)
.subscribe();

The output is the following :

enter image description here

(notice that we have the intermediate results !)

like image 23
maxime1992 Avatar answered Nov 30 '25 18:11

maxime1992



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!