-5

I have learned about group by at https://www.learnrxjs.io/operators/transformation/groupby.html

However when I tried to make a group by to the column 'type' only and I don't get the requested result.

What part am I missing in order to make a group by to the column 'type' and then display at component.html?

You get the data of type from the subscribe and you applyy the group by code inside of the subscribe.

Thank you!

Stackblitz = https://stackblitz.com/edit/angular-twngjm

HelloWorld1
  • 13,688
  • 28
  • 82
  • 145
  • can you give your expected and realized behavior – bryan60 Aug 17 '19 at 22:39
  • You're http request returns an array and you want to group the items in the array so you don't have to use RxJS. You could use a solution from here: [Most efficient method to groupby on an array of objects](https://stackoverflow.com/questions/14446511/most-efficient-method-to-groupby-on-an-array-of-objects). If you want to use RxJS you would have the overhead of turning the array into an Observable that emits each item, use `groupBy`, turn the Observable back into an array of arrays. – frido Aug 17 '19 at 22:40

1 Answers1

2

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;
  }, {});
}
bryan60
  • 28,215
  • 4
  • 48
  • 65