[Solved] Use RxJS’s group by at inside of Subscribe [closed]


how you do it:

service:

Dataa() {
  return this._http.get<any[]>('https://api.github.com/users'); // not a promise
}

component:

_test; // not private

ngOnInit() {
  this._test = this._apiService.Dataa().pipe( // set observable to variable
    switchMap(data => from(data).pipe( // array to stream
      groupBy((item: any) => item.type), // group by type
      mergeMap(group => zip(of(group.key), group.pipe(toArray()))), // convert each group back to array and zip with key
      reduce((acc, val) => acc.concat([val]), []) // collect emissions into array of arrays
    ))
  );
}

template:

<div style="display: flex;"> <!-- just styles into columns -->
  <div *ngFor="let item of _test | async"> <!-- subscribe with async and iterate grouped arrays -->
    {{item[0]}} <!-- first item is headers, display it -->
    <div *ngFor="let user of item[1]"> <!-- second item is the grouped items, which you iterate -->
      {{user.login}} <!-- show your data here -->
    </div>
  </div>
</div>

blitz: https://stackblitz.com/edit/angular-cbnkjv?file=src%2Fapp%2Fapp.component.ts

explanation:

first, no need to convert to promises here. use observables. also variables accessed in template can’t be private.

next, your data is already an array, so you need to convert it to a stream using switchMap -> from so that groupBy can operate on it, as groupBy only works on streams.

groupBy produces a GroupedObservable which is basically a bunch of streams of items grouped by the specified property in groupBy.

so, after that, you need to mergeMap into those streams. and you zip it with the group key, and convert the stream to an array, you get a structure like [groupKey, […items in group…]].

finally, mergeMap will emit each group one by one, so you use reduce to collect them all into an array of arrays

then you modify your template to work with this array of arrays structure with nested ngFors. I chose to use the async pipe as well to handle my subscription.

discussion:

is this all worth it? Probably not unless you’re truly dealing with a stream of data, like from a web socket or a stream of user actions. There is no need to force your array to an observable, group, and then convert back to array and collect. You can just use some utility library like lodash and run a map operator on it synchronously. the code would be reduced to this if you did:

ngOnInit() {
  this._test = this._apiService.Dataa().pipe(
    map(data => _.groupBy(data, 'type')),
    map(grouped => Object.entries(grouped)) // _.groupBy gives a keyed object, this just converts to the same array of array structure as rx groupBy
  );
}

really don’t want another dependency? simple grouping function:

function groupBy(arr, prop) {
  return arr.reduce((acc, val) => {
    const key = val[prop];
    acc[key] = (acc[key] || []).concat([val]); 
    return acc;
  }, {});
}

0

solved Use RxJS’s group by at inside of Subscribe [closed]