15

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

peinearydevelopment
  • 11,042
  • 5
  • 48
  • 76
dunaskdunsay
  • 151
  • 1
  • 1
  • 3
  • 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 May 14 '18 at 19:56
  • I would like to group this by GroupID and get them as array back. Now i start with Observable (cause of the [] groupby didnt get me) Maybe I just need to turn this Observable to something like normal array and use the normal groupby. Do you know how to turn it to normal array? – dunaskdunsay May 15 '18 at 06:27
  • 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 May 15 '18 at 07:14
  • 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 May 15 '18 at 08:08
  • Did you find any solution? I am also confused why groupBy is giving me back an array so I can't group.. – supereschek Sep 14 '18 at 10:46

4 Answers4

17

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)]
Kamil
  • 1,456
  • 4
  • 32
  • 50
  • Thanks a lot for this answer, I looking for this a lot, – Mary Aug 06 '20 at 15:36
  • This is quite interesting :) Can you elaborate on your answer? – XRaycat Jan 10 '21 at 21:15
  • This answer did not help me. Actually it returns: `"TypeError: You provided 'function zipOperatorFunction(source) {"` [This was what I was looking for](https://stackoverflow.com/a/61652403/1555615) – Marinos An Jun 22 '21 at 17:23
13

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}
  ]
}
Marinos An
  • 9,481
  • 6
  • 63
  • 96
Ingo Bürk
  • 19,263
  • 6
  • 66
  • 100
  • 1
    Thanks for your help and your time. However in case of the Higher-Order Observable: from(data).pipe( groupBy(item => item.groupId) ) I tried this before but it didn’t work because I have an array of Observable, ist not just plain object like Object ; It’s an Object[] groupId is unknow in item, because item in this case is an array of object [] But if i go further : groupBy(books => books.map(book => book.groupId)) it thinks i take array as key and turns out wrong. I will give observable of groups a try. – dunaskdunsay May 15 '18 at 12:47
2

Just use the groupBy operator, then merge them all.

$data.pipe(
    groupBy(book => book.groupId),
    mergeMap(group => group.pipe(toArray()))
)
.subscribe(console.log)
Luillyfe
  • 6,183
  • 8
  • 36
  • 46
  • it doesn't work with Observable. I got an Array as key back. – dunaskdunsay May 15 '18 at 06:25
  • 1
    Rxjs 6.3.2 + Angular 6.1.8 -> what @dunaskdunsay says, you you get an array back. In the code example below, typescript gives the error 'property age does not exist on type '{ name: string; age: number; }[]' `const people = [{ name: 'Sue', age: 25 }, { name: 'Joe', age: 30 }, { name: 'Frank', age: 25 }, { name: 'Sarah', age: 35 }]; const source = of(people); const example = source.pipe(groupBy(p => p.age)); example.subscribe(val => console.log(val));` – Mcanic Sep 28 '18 at 15:14
0
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 }]
}