How to Group Data and Find Maximums by Group in Angular using RxJS

How to Group Data and Find Maximums by Group in Angular using RxJS

Have you ever faced an interview question where you need to take a flat list of employee records and group them by department to find the highest earner?
While it sounds straightforward, doing this reactively with RxJS can quickly lead to tricky bugs if operators are chained out of order. In this post, we will look at a common broken snippet, dissect why it fails, and implement two clean ways to solve it.

The Problem Scenario

Imagine you have the following flat dataset inside an Angular component:
employees = [
  { id: 0, name: "Mani", dept: "IT", salary: 12000},
  { id: 1, name: "Lava", dept: "IT", salary: 1200},
  { id: 2, name: "Nila", dept: "Medical", salary: 12000},
  { id: 3, name: "Test", dept: "IT", salary: 12000},
  { id: 4, name: "JHi", dept: "Medical", salary: 12000},
  { id: 5, name: "ani", dept: "IT", salary: 12000},
  { id: 6, name: "Mai", dept: "IT", salary: 12000},
];
The goal is to calculate the maximum salary department-wise and output a clean structure like this:
[
  { "department": "IT", "maxSalary": 12000, "employeeName": "Mani" },
  { "department": "Medical", "maxSalary": 12000, "employeeName": "Nila" }
]

✅ Solution 1: The Correct RxJS Reactive Stream

To do this properly using RxJS, you must manipulate the data inside the inner group pipeline before sending it back to the main stream.
import { from } from 'rxjs';
import { groupBy, toArray, mergeMap, map } from 'rxjs/operators';

action() {
  from(this.employees).pipe(
    // 1. Split the flat stream into separate GroupedObservables by department
    groupBy(emp => emp.dept),
    
    // 2. Intercept each group stream independently
    mergeMap(group => 
      group.pipe(
        // 3. Collect all employees matching this specific group into an array
        toArray(),
        
        // 4. Use JavaScript reduce on the array to find the top earner
        map(users => {
          const topEarner = users.reduce((max, current) => 
            current.salary > max.salary ? current : max
          );
          
          return {
            department: group.key, // group.key is fully valid here!
            maxSalary: topEarner.salary,
            employeeName: topEarner.name
          };
        })
      )
    ),
    // 5. Package all resolved department objects into one final array
    toArray()
  )
  .subscribe(result => {
    this.data = result;
  });
}

⚡ Solution 2: The High-Performance Native JavaScript Way (Pro-Tip)

If an interviewer asks you this question, a fantastic way to stand out is to offer architectural nuance.
Ask yourself: Do we actually need RxJS streams here?
Since this.employees is already a local, synchronous synchronous array, using an RxJS pipeline adds overhead. You can compute this much faster using standard array .reduce():
action() {
  const topEarners = this.employees.reduce((accumulator, current) => {
    const department = current.dept;
    
    // If the department doesn't exist yet, or we found a higher salary
    if (!accumulator[department] || current.salary > accumulator[department].maxSalary) {
      accumulator[department] = {
        department: department,
        maxSalary: current.salary,
        employeeName: current.name
      };
    }
    return accumulator;
  }, {} as Record<string, { department: string; maxSalary: number; employeeName: string }>);

  // Extract the dictionary values back into a clean array
  this.data = Object.values(topEarners);
}

Why mention this in an interview?

  • Time Complexity: It iterates over the array exactly once, running in O(N) time.
  • Readability: It avoids complex RxJS imports and sub-pipelines.
  • Pragmatism: It proves you know when not to use a library when native features are cleaner.

Summary Checklist for RxJS Grouping

  • Creation methods like from() live in 'rxjs'.
  • Pipeable operators like groupBy, mergeMap, and toArray live in 'rxjs/operators'.
  • Always use mergeMap() right after groupBy() to handle the inner streams individually.
  • Remember to import JsonPipe into your Angular component's imports array if you want to test output via {{ data | json }}!


Comments

Popular posts from this blog

SonarQube With Angular 19 on Windows: A Complete Setup and Integration Guide

SwiftUI - Tutorial 2 - 🧵 Understanding @MainActor in SwiftUI — A Beginner’s Guide