3

So the requirement is like: I got a array of objects (e.g. Array)

[
   { empDept: 'Engineering', empLoc: 'Pune', empName: 'John' },
   { empDept: 'Engineering', empLoc: 'Mumbai', empName: 'Harry' },
   { empDept: 'HR', empLoc: 'Pune', empName: 'Denis' },
   { empDept: 'Finance', empLoc: 'Mumbai', empName: 'Elvis' },
]

I will have a function which will accept the name of the property as a parameter, on which the employee list will be grouped. So if the input parameter value is 'empDept', then I want the result as below:

[
   {
      key: 'Engineering',
      values: [
         { empDept: 'Engineering', empLoc: 'Pune', empName: 'John' },
         { empDept: 'Engineering', empLoc: 'Mumbai', empName: 'Harry' },
     ]
   },
   {
      key: 'HR',
      values: [
         { empDept: 'HR', empLoc: 'Pune', empName: 'Denis' },
     ]
   },
   {
      key: 'Finance',
      values: [
        { empDept: 'Finance', empLoc: 'Mumbai', empName: 'Elvis' },
     ]
   }
]

I'm using the RxJs groupBy operator to achieve this. I have tried the below code, but it doesn't seem to give the desired result:

this.groupedEmployees$ = employees$.pipe(
      groupBy(person => person.empDept),
      mergeMap(group => of({ key: group.key, values: group.pipe(mergeMap(x => toArray())) })),
    );

Appreciate your help.

Lucifer
  • 2,317
  • 9
  • 43
  • 67
  • Does this answer your question? [RXJS groupBy Observable ](https://stackoverflow.com/questions/50332149/rxjs-groupby-observable-object) – frido May 06 '20 at 20:50
  • Also: [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) – frido May 06 '20 at 20:58

3 Answers3

9

If you really want to stick with RxJS, you can use this piece of code:

import { from } from 'rxjs'; 
import { groupBy, mergeMap, reduce, toArray } from 'rxjs/operators';

const groupedEmployees$ = from([
  { empDept: 'Engineering', empLoc: 'Pune', empName: 'John' },
  { empDept: 'Engineering', empLoc: 'Mumbai', empName: 'Harry' },
  { empDept: 'HR', empLoc: 'Pune', empName: 'Denis' },
  { empDept: 'Finance', empLoc: 'Mumbai', empName: 'Elvis' }
]).pipe(
  groupBy(person => person.empDept),
  mergeMap(group => group
    .pipe(
      reduce((acc, cur) => {
          acc.values.push(cur);
          return acc;
        },
        { key: group.key, values: [] }
      )
    )
  ),
  toArray()
);

groupedEmployees$.subscribe(console.log);
Mladen
  • 2,070
  • 1
  • 21
  • 37
  • Thanks, but why does it console.log twice, separately for each key-value pairs. I fixed it by adding a toArray() operator. – Lucifer May 09 '20 at 18:35
  • I'm sorry, I didn't notice you wanted an array of these objects. Yes, adding `toArray()` would convert it to what you wanted. I'll edit my question. – Mladen May 09 '20 at 21:01
0

Why would you use rxjs in this case? It's purely array transformation. You can use lodash groupBy: https://lodash.com/docs/#groupBy


const t = [
   { empDept: 'Engineering', empLoc: 'Pune', empName: 'John' },
   { empDept: 'Engineering', empLoc: 'Mumbai', empName: 'Harry' },
   { empDept: 'HR', empLoc: 'Pune', empName: 'Denis' },
   { empDept: 'Finance', empLoc: 'Mumbai', empName: 'Elvis' },
];

const result = Object.entries(_.groupBy(t, 'empDept')).map(([key, values]) => ({key, values}));
HTN
  • 3,388
  • 1
  • 8
  • 18
  • Thanks for you answer. But I prefer RxJs because I'm dealing with observables. – Lucifer May 09 '20 at 18:34
  • That's what we call overkill: you make it more complex than it should be. Do not forget rxjs purpose: `RxJS is a library for composing asynchronous and event-based programs by using observable sequences. It provides one core type, the Observable, satellite types (Observer, Schedulers, Subjects) and operators inspired by Array#extras (map, filter, reduce, every, etc) to allow handling asynchronous events as collections.` and `Think of RxJS as Lodash for events.` – HTN May 10 '20 at 08:36
  • Why would using RxJS be considered an overkill whilst using Lodash wouldn't? – Mladen May 10 '20 at 15:54
  • You can even do it without Lodash. With lodash, you can write it in 1 line like I did, with only one method `groupBy`. How many lines did you write with `rxjs`, and with how many functions? – HTN May 10 '20 at 22:57
  • 1
    Yes, Lodash fixes this problem in one line, but what if OP's `employees$` Observable is an asynchronous source that was already handled by RxJS so they just wanted to continue using RxJS not wanting to mix different approaches (e.g. adding Lodash solution)? I would call using two libraries as an overkill. And I'm not sure how would you solve this in one line without Lodash. – Mladen May 11 '20 at 06:48
  • Mixing libs is not a problem if they are modular. In case of lodash, use `lodash-es`. If you want to transform an Observable, there is `map` operator. – HTN May 11 '20 at 07:35
0

Are you looking for something like this?

function groupBy(key, array) {
  return array.reduce((all, current) => {
    const existingKey = all.find(existing => existing.key === current[key]);
    if (!existingKey) {
      all.push({key: current[key], values: [current]});
    } else {
      existingKey.values.push(current);
    }
    return all;
  }, []);
}

Example:

const arr = [
   { empDept: 'Engineering', empLoc: 'Pune', empName: 'John' },
   { empDept: 'Engineering', empLoc: 'Mumbai', empName: 'Harry' },
   { empDept: 'HR', empLoc: 'Pune', empName: 'Denis' },
   { empDept: 'Finance', empLoc: 'Mumbai', empName: 'Elvis' },
];

const byDept = groupBy('empDept', arr);
const byLoc = groupBy('empLoc', arr);
Damian Plewa
  • 1,123
  • 7
  • 13