Avoiding Nested Subscriptions in Angular

The code for this article is available on my GitHub:
https://github.com/JeffreyBane/ALF-My-Nested-Subscription-App

Nested subscriptions are a big no-no in Angular for many reasons. Nested subscriptions can lead to an unmanageable mess of multiple (even hundreds or thousands of) subscriptions that can cause memory leaks and destroy the performance of your site.

Even worse for the developer – code inspection tools like SonarQube won’t let you get away with nested subscriptions, potentially making your code undeployable.

Say with me – “Nested subscriptions are bad and I promise I’m going to read the links at the end of this article. HOWEVER, I really need to fix this right now as my current sprint is over in 6 minutes.” Ok, fair enough.

Let’s look at a common use case.

Say you have a form with a dropdown containing a list of items from a master table, in our case we’ll say it’s a list of departments. You’re also retrieving an entity from a database that contains a foreign key value to that list, in our example, an employee record is returned with a Department ID foreign key.
Once you have the employee record, you’ll display an Employee Edit form, displaying various employee related fields on the form as well as select the value in the dropdown list that displays the employee’s current department.
This dropdown also allows the department to be changed by selecting a different value in the dropdown:

The “problem” with this is you can’t just fire off a bunch of async observables and hope to set the Department ID value properly. You have to wait until the dropdown list is populated before you can set the selected id value, so we need a synchronous operation.

Your first thought might be, let me call an observable to retrive the list of department entities then populate a variable bound to the dropdown.
ONCE THAT LIST IS POPULATED, (<= main point here), I’ll grab the employee data and then change the selected index of the dropdown to the Department ID of the employee in question.

So plan A becomes:

  • Subscribe to the Departments observable.
  • In the complete function of the subscribe, bind the data to the Departments dropdown to populate it. Now call and subscribe to the observable that retrieves employee data.
  • Once that observable completes, set the correct Department ID value in the dropdown.

Let’s have a look at that code with the above nested subscription. First let’s look at the form and then the service we’ll be using:

Employee Form (employee.component.html)

<p>Employee Edit</p>
<form [formGroup]="employeeForm">

Name: <input type="text" value="{{ name }}"/><br>
Status: <input type="text" value="{{ status }}"/><br>

Department: <select formControlName="departmentsSelect">
    <option *ngFor="let d of departments" [value]="d.deptId">
        {{d.departmentName}}
    </option>
</select>
<br>

<button>Submit</button>
</form>

Employee Service (employee.service.ts)

import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class EmployeeService {

  constructor() { }

  getEmployeeDetails(empId: number): Observable<any> {
   const mockJson = {name: 'John Doe', status: 'Active', deptId: 2 };
   return of(mockJson);
  }

  getDepartmentList(): Observable<any[]> {
    const mockJson = [
    {deptId: 1, departmentName: 'Human Resources'},
    {deptId: 2, departmentName: 'Development'},
    {deptId: 3, departmentName: 'Sales'}
    ];
    return of(mockJson);

  }
}

So two very simple mocked service calls, get the employee details and get the list of departments. Now here’s our component file that uses a nested subscription:

Employee Component (employee-nested.component.ts)

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Subscription } from 'rxjs';
import { EmployeeService } from './employee.service';

@Component({
  selector: 'employee',
  templateUrl: './employee.component.html',
})
export class EmployeeNestedComponent implements OnInit {

  departments: any[];
  name = '';
  status = '';
  deptId = 0;
  departmentsSelect: FormControl;
  employeeForm: FormGroup;
  sub1: Subscription;
  sub2: Subscription;



  constructor(private _employeeService: EmployeeService) { }

  ngOnInit(): void {


    this.departmentsSelect = new FormControl();

    this.employeeForm = new FormGroup({
      departmentsSelect: this.departmentsSelect
    }
    );


      this.sub1 = this._employeeService.getDepartmentList()
         .subscribe((deptData) => {
         this.departments = deptData;
         this.sub2 = this._employeeService.getEmployeeDetails(123) 
            .subscribe(data => {

            // Set selected dropdown value:
            this.employeeForm.controls.departmentsSelect.setValue(data.deptId); 
            this.status = data.status;
            this.name = data.name;

      });
    });

  }

}

In the above, our nested subscribe has the effect of waiting until the getDepartmentList() call is complete before firing off getEmployeeDetails(123), and ultimately setting the correct dropdown value in the nested subscribe.
Now technically this works. However, notice we have 2 subscriptions to manage, sub1 and sub2. You can also imagine how this would look if we had say 3, 4 or 10 nested subscriptions. But again, most importantly – this is not getting past static code analysis – Sonarqube and similar products will publicly mock you for using a nested subscription.

What’s the better way? Plan B – Enter the higher order mapping operators. In short, these operators transform an observable into another observable.
Each value from an “outer” observable is mapped to a new “inner” observable.
This inner observable is then automatically subscribed to (and unsubscribed from).

Let’s have a look at an example. In this case we’ll use concatMap, although it’s not the only option (switchMap would work well here also. See switchMap in action in this article: Implementing a Typeahead search in Angular using switchMap), but concatMap has some neat behavior you’ll want to know about for your code arsenal. In a nutshell, concatMap does not subscribe to the next observable until the previous completes – essentially it’s the way to run observables in order, synchronously.


You can imagine this having value in many use cases, not just our dropdown example. For instance, if you ever have some complex database operations where an insert MUST occur before an update, or a series on inserts must occur in order, etc. So, let’s have a look at our new employee component, nested subscription eliminated, now using concatMap:

Better Employee Component (employee-concat-map.component.ts)

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { concatMap, Subscription } from 'rxjs';
import { EmployeeService } from './employee.service';

@Component({
  selector: 'employee',
  templateUrl: './employee.component.html',
})
export class EmployeeConcatMapComponent implements OnInit {

  departments: any[];
  name = '';
  status = '';
  deptId = 0;
  departmentsSelect: FormControl;
  employeeForm: FormGroup;
  sub1: Subscription;



  constructor(private _employeeService: EmployeeService) { }


  ngOnInit(): void {

    this.departmentsSelect = new FormControl();

    this.employeeForm = new FormGroup({
      departmentsSelect: this.departmentsSelect
    }
    );



    this.sub1 = this._employeeService.getDepartmentList().pipe(
      concatMap((deptData) => {
        this.departments = deptData;
        return this._employeeService.getEmployeeDetails(123);
        }))
.subscribe(data => {

           // Set selected dropdown value:
           this.employeeForm.controls.departmentsSelect
           .setValue(data.deptId);
           this.status = data.status;
           this.name = data.name;

      }
);
  }
}

So what did we do here? To use the higher order mapping operators, the results of the first observable (get our departments dropdown) aren’t subscribed to directly but “piped” to the concatMap operator. Once getDepartmentList() completes, the result of it (deptData) is assigned to our local variable this.departments which is bound to our select list. Now it returns the results of our second observable (getEmployeeDetails(123)) and that result is what is subscribed to! Once that completes, we can set our correct dropdown value along with the other employee related fields.

This is good code – static code analyzers will like this code. Notice also we only have one subscription to manage now instead of two, since concatMap automatically subscribes and unsubscribes from the inner observable (getEmployeeDetails). We’ll touch on subscription management in another article as well as when you have to unsubscribe from certain observables (which is a topic of much confusion). But for now, you can see how nested observables could lead to many subscriptions whereas we can use just one subscription when using higher order mapping operators. The end result is much cleaner and more durable code.

Now you go read please:


Higher order mapping operators (also has a bit on why nested subscriptions are bad)
https://blog.angular-university.io/rxjs-higher-order-mapping/#:~:text=Angular%20University,-21%20Jan%202022&text=Some%20of%20the%20most%20commonly,%2C%20mergeMap%2C%20concatMap%20and%20exhaustMap.


More on higher order mapping and why nested subscriptions are bad:
https://blogs.msmvps.com/deborahk/higher-order-observable/

3 thoughts on “Avoiding Nested Subscriptions in Angular”

Leave a Reply

Your email address will not be published. Required fields are marked *