Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RXJS groupBy Observable <Object[]>

I have entries$: Observable<BooksOverviewGroup[]>;:

enter image description here

and i would like to group them by groupId. I tried like this:

groupBy(books => books.map(book => book.groupId)),
 mergeMap(innerObs => innerObs.skip(1).map(books => books.map(book => book.groupTitle)));

However, it didn't work. How can I group by groupId in this case ? (Observable<Object[]>)

like image 487
dunaskdunsay Avatar asked May 14 '18 13:05

dunaskdunsay


3 Answers

You almost got it.

Try using mergeMap() or concatMap() before groupBy().

So an example:

const people = [
  { name: 'Sue', age: 25 },
  { name: 'Joe', age: 30 },
  { name: 'Frank', age: 25 },
  { name: 'Sarah', age: 35 }
];

of(people) // <- this gets Observable<Object[]>
  .pipe(
    mergeMap(res => res), // <- use concatMap() if you care about the order
    groupBy(person => person.age, p => p.name),
    mergeMap(group => zip(of(group.key), group.pipe(toArray())))
  )
  .subscribe(console.log);

Output:

(2) [25, Array(2)]
(2) [30, Array(1)]
(2) [35, Array(1)]
like image 56
Kamil Avatar answered Oct 19 '22 21:10

Kamil


Higher-Order Observable

If you want a higher-order observable, you can actually use the groupBy rxjs operator.

const data = [
  {groupId: "foo", value: 1},
  {groupId: "foo", value: 3},
  {groupId: "foo", value: 5},
  {groupId: "bar", value: 42},
  {groupId: "bar", value: 1337},
];

from(data).pipe(
  groupBy(item => item.groupId)
)
  .subscribe(console.log);

This will result in an observable which emits one observable per group, and on each of those inner observables, the corresponding items will be emitted.

Observable of Groups

If instead you want an observable which simply emits the groups, then this has actually nothing to do with rxjs or its groupBy operator. Instead, it's merely a basic question about how to group an array in Javascript.

There is no built-in method for this, but libraries like lodash come with such functions. You can also do it by hand:

const data = [
  {groupId: "foo", value: 1},
  {groupId: "foo", value: 3},
  {groupId: "foo", value: 5},
  {groupId: "bar", value: 42},
  {groupId: "bar", value: 1337},
];

const groupBy = (data, keyFn) => data.reduce((agg, item) => {
  const group = keyFn(item);
  agg[group] = [...(agg[group] || []), item];
  return agg;
}, {});

of(data).pipe(
  map(data => groupBy(data, item => item.groupId))
)
  .subscribe(console.log);

This will emit once for the entire data set, but will emit

{
  "foo":[
    {"groupId":"foo","value":1},
    {"groupId":"foo","value":3},
    {"groupId":"foo","value":5}
  ],
  "bar":[
    {"groupId":"bar","value":42},
    {"groupId":"bar","value":1337}
  ]
}
like image 13
Ingo Bürk Avatar answered Oct 19 '22 21:10

Ingo Bürk


Just use the groupBy operator, then merge them all.

$data.pipe(
    groupBy(book => book.groupId),
    mergeMap(group => group.pipe(toArray()))
)
.subscribe(console.log)
like image 1
Luillyfe Avatar answered Oct 19 '22 20:10

Luillyfe