Search code examples
angulartypescriptgroup-byrxjsmergemap

How to Group by user, type and sum a few properties in angular using rxjs


enter image description here

I want to group the user by type(contractor or staff) and sum the Unit(words / hrs) and Cost.

I have tried the following code but no luck.

this.jobsGroupBy = of(this.jobDetails).pipe(
              groupBy((val:any) => val.userId && val.cont),
              mergeMap((val$:any) =>
                val$.pipe(
                  reduce((acc:any, curr:any) => {
                    acc.tCost = Number(acc.tCost) + Number(curr.tCost);
                    this.jobsGroupBy = acc;
                    return acc;
                  })
                )
              )
            );
console.log('result :'+ JSON.stringify(this.jobsGroupBy));

The output is: result :{"source":{}}


Solution

  • In my opinion you do not really need rxjs for these scenarios, you can solve this with simple js reduce, But if rxjs is essential please check the next block of rxjs code.

    const reduced = jobDetails.reduce((acc: any, val: any) => {
      const key = `${val.userId}-${val.cont}-${val.unit}`;
      if(!acc[key]) {
        acc[key] = val;
      } else {
        acc[key]['amount'] += val['amount'];
        acc[key]['tCost'] += val['tCost'];
      }
      return acc;
    }, {});
    
    console.log('result :' + JSON.stringify(Object.values(reduced)));
    

    Stackblitz Demo


    We need to use from to convert the array, into a stream of observables.

    Then we use groupBy to group based on a key ${val.userId}-${val.cont}-${val.unit} I am grouping by userId, cont and unit.

    After grouping, we will get distinct arrays of rows matching the groups, which we can use reduce to sum up the number fields and finally return the result one by one.

    Optionally you can use toArray to sum up all the results and return it one one go.

    from(jobDetails)
      .pipe(
        groupBy((val: any) => `${val.userId}-${val.cont}-${val.unit}`),
        mergeMap((group: any) =>
          group.pipe(
            reduce((acc: any, curr: any) => {
              if (!acc) {
                acc = curr;
              } else {
                acc['amount'] += curr['amount'];
                acc['tCost'] += curr['tCost'];
              }
              return acc;
            }, null)
          )
        )
        // toArray(), // <- returns only once when enabled
      )
      .subscribe((jobsGroupBy: any) => {
        console.log('result :' + JSON.stringify(jobsGroupBy));
      });
    

    Full Code:

    import { groupBy, mergeMap, reduce, toArray } from 'rxjs/operators';
    import './style.css';
    
    import { rx, of, map, from, zip } from 'rxjs';
    
    // Open the console in the bottom right to see results.
    
    const jobDetails = [
      { userId: 'test1', cont: 'staff', unit: 'Hours', amount: 0.5, tCost: 15 },
      { userId: 'test1', cont: 'staff', unit: 'Words', amount: 1500, tCost: 15 },
      { userId: 'test1', cont: 'staff', unit: 'Hours', amount: 1.5, tCost: 53 },
      { userId: 'test1', cont: 'staff', unit: 'Words', amount: 8576, tCost: 230 },
      { userId: 'test1', cont: 'contrator', unit: 'Hours', amount: 0.5, tCost: 15 },
      {
        userId: 'test1',
        cont: 'contrator',
        unit: 'Words',
        amount: 1500,
        tCost: 15,
      },
      { userId: 'test1', cont: 'contrator', unit: 'Hours', amount: 1.5, tCost: 53 },
      {
        userId: 'test1',
        cont: 'contrator',
        unit: 'Words',
        amount: 8576,
        tCost: 230,
      },
      { userId: 'test2', cont: 'staff', unit: 'Hours', amount: 0.5, tCost: 15 },
      { userId: 'test2', cont: 'staff', unit: 'Words', amount: 1500, tCost: 15 },
      { userId: 'test2', cont: 'staff', unit: 'Hours', amount: 1.5, tCost: 53 },
      { userId: 'test2', cont: 'staff', unit: 'Words', amount: 8576, tCost: 230 },
      { userId: 'test2', cont: 'contrator', unit: 'Hours', amount: 0.5, tCost: 15 },
      {
        userId: 'test2',
        cont: 'contrator',
        unit: 'Words',
        amount: 1500,
        tCost: 15,
      },
      { userId: 'test2', cont: 'contrator', unit: 'Hours', amount: 1.5, tCost: 53 },
      {
        userId: 'test2',
        cont: 'contrator',
        unit: 'Words',
        amount: 8576,
        tCost: 230,
      },
    ];
    
    from(jobDetails)
      .pipe(
        groupBy((val: any) => `${val.userId}-${val.cont}-${val.unit}`),
        mergeMap((group: any) =>
          group.pipe(
            reduce((acc: any, curr: any) => {
              if (!acc) {
                acc = curr;
              } else {
                acc['amount'] += curr['amount'];
                acc['tCost'] += curr['tCost'];
              }
              return acc;
            }, null)
          )
        )
        // toArray(), // <- returns only once when enabled
      )
      .subscribe((jobsGroupBy: any) => {
        console.log('result :' + JSON.stringify(jobsGroupBy));
      });
    

    Stackblitz Demo