15
votes

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[]>)

4
Do you actually want to group it into a higher order observable? Or do you actually mean to convert it into an observable of a list of groups? - Ingo Bürk
I would like to group this by GroupID and get them as array back. Now i start with Observable<Object[]> (cause of the [] groupby didnt get me) Maybe I just need to turn this Observable<Object[]> to something like normal array and use the normal groupby. Do you know how to turn it to normal array? - dunaskdunsay
You didn't answer my question. Do you want a higher order observable (an observable emitting one observable per group) or a single observable that emits groups? - Ingo Bürk
Sorry for that, I would like to see both variants to learn how to process the data. If it's too costly, I would like to see the solution for the higher order observable. Thank you. - dunaskdunsay
Did you find any solution? I am also confused why groupBy is giving me back an array so I can't group.. - supereschek

4 Answers

14
votes

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}
  ]
}
13
votes

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)]
1
votes

Just use the groupBy operator, then merge them all.

$data.pipe(
    groupBy(book => book.groupId),
    mergeMap(group => group.pipe(toArray()))
)
.subscribe(console.log)
0
votes
const data = [
    { groupId: "foo", value: 1 },
    { groupId: "foo", value: 3 },
    { groupId: "foo", value: 5 },
    { groupId: "bar", value: 42 },
    { groupId: "bar", value: 1337 },
];

of(data).pipe(
    mergeMap(res => res),
    groupBy(item => item.groupId),
    mergeMap(obs => {
        return obs.pipe(
            toArray(),
            map(items => {
                return { [obs.key]: items }
            })
        )
    }), toArray()
).subscribe(map((groupResponse) => {
    let groups = {};
    groupResponse.forEach((item) => { groups = { ...groups, ...item }; });
    console.log(groups);
});

Output will be

{
    "foo": [{ "groupId": "foo", "value": 3 }, { "groupId": "foo", "value": 5 }],
    "bar": [{ "groupId": "bar", "value": 42 }, { "groupId": "bar", "value": 1337 }]
}