RxJS in Angular – Antipattern 1 – Nested subscriptions

Working on numerous Angular projects over the past couple of years, it has become evident, that a lot of developers still have a hard time working with RxJS which finds its use in Angular. While RxJS is a very powerful tool, it comes with a lot of operators and a whole paradigm shift. Even experienced developers can be hung up on its intricacies and more than 100 operators.

In diesem Artikel:

RxJS in Angular – Antipattern 1 – Nested subscriptions
Yannick Baron ist Architekturberater bei Thinktecture mit einem Fokus auf Angular und RxJS.

In this first part of a small series, I want to share some pitfalls we discovered multiple times in code reviews over the years, as well a few patterns we identified as helpful.

Nested subscriptions in use

A very common use case is to request a data object from our API to then use some of its properties to request a few more related entities. Finally, we combine the received objects to fill our view.

In Angular we could implement it like this:

  switchMap(({ customerId }) => customerService.getCustomer(customerId)),
).subscribe(customer => {
  this.customer = customer;

    .subscribe(address => this.address = address);

    .subscribe(orders => this.orders = orders);

As implemented above, when the customerId in our route changes, we request a customer data object from the API. Once received, we use information on the object to request the related address data object and a couple of order records. All our data objects are assigned to a property on our component becoming part of the view.

We have encountered similar implementations on multiple occasions in code reviews and in the following I want to discuss downsides of this implementation as well as suggest a possible fix.

The Bad

Please notice how there are two subscribes nested in the outer subscribe on the getCustomer request.

In this case we have to handle and clean up three subscriptions (which we omitted in the above example for brevity). If we do not do this properly we can run into various problems, so please clean up after yourself!

With the code above we open up the possibility of a timing issue. Please assume the following sequence of events:

  1. our user navigates to the detail page of Customer1
  2. request Customer1 data object
  3. retrieve Customer1 and update view
  4. request address data for Customer1 … taking a while due to the API choking
  5. the impatient user of our application navigates to detail page of Customer2
  6. request Customer2 data object
  7. retrieve Customer2 and update view
  8. request address data for Customer2
  9. retrieve address data for Customer2 and update view
  10. retrieve address data for Customer1 and update view … finally resolved!

Due to the delay in the response of the first address, the view is updated with the first address after the second address has been loaded. This results in us displaying the second customer with the first customers’s address!

In the following, I tried to illustrate the above behavior in a more simplified fashion:

					// Emitting customerIds 1 and 2 with a 25ms delay
createStream<number>([1, 2], 25)
  .subscribe(id => {
    // Update the view with the received customer object
    updateView('customer', `Customer #${id}`)

    // Request address for customer but delay address for Customer #1 by 1000ms
      .subscribe(address => updateView('address', address));

And you can run this implementation here:

You will find that the view is updated twice, as the first request for an address is delayed past the retrieval of the second customers’s information, thus resulting in the mismatch of information.

The Fix

In order to fix this behavior, we make use of the switchMap operator. After receiving the customer id (or the customer) we switch to the stream requesting the address. Furthermore, we use map to combine the customer and the retrieved address into a single object containing both. Therefore, our stream will carry this bundle of all the information we need to update our view in a single subscription, thus preventing a mismatch.

As an added bonus, switchMap unsubscribes from the inner stream for us, therefore, the request for the first address will be canceled, as soon as the second customer is emitted.

					// Emitting customerIds 1 and 2 with a 25ms delay
createStream<number>([1, 2], 25)
  // Switch stream to the address request
  .pipe(switchMap(id => {
    // Received customer object
    const customer = `Customer #${id}`; 

    return requestAddress(id)
      // Combine the customer object and the resolved address to a single object
      .pipe(map(address => ({ customer, address })));
  .subscribe(({ customer, address }) => {
    // Update the view with the matching information
    updateView('customer', customer);
    updateView('address', address);


Please see this fix for yourself:


As a little bonus I recreated the initial problem statement more faithfully. In this demo I am simulating a request for a customer object, and requesting multiple related entities, making use of zip:


We have illustrated how nesting subscriptions can result in timing issues causing unforeseen side-effects. Furthermore, the more subscriptions you have, the more you have to manage unsubscribing, cleanup actions and handling errors. Most of the time the goal should be to use the operators RxJS provides us with to combine our streams to yield a single value. This makes it easier to argue about how our data flows through the stream with no added side-effects.

In the next part of this series we want to discuss another rather common pitfall we have encountered in various reviews and how employing above techniques will help us a different form of potential side-effects.


Aktuelle Artikel, Screencasts, Webinare und Interviews unserer Experten für Sie

Verpassen Sie keine Inhalte zu Angular, .NET Core, Blazor, Azure und Kubernetes und melden Sie sich zu unserem kostenlosen monatlichen Dev-Newsletter an.

Diese Artikel könnten Sie interessieren
Configuring Lazy Loaded Angular Modules

Configuring Lazy Loaded Angular Modules

Making our Angular modules configurable is an important step in building a reusable architecture. Having used Angular for a while you might be familiar with the commonly used forRoot() and forChild() functions, that some modules provide you with. But what is the best way to provide configuration in these cases?
Master Web Component Forms Integration – with Lit and Angular

Master Web Component Forms Integration – with Lit and Angular

When a company has cross-framework teams, it is a good choice to use Web Components to build a unified and framework-independent component library. However, some pitfalls are to consider when integrating these components into web forms. Therefore, for a better understanding, we will look at two possible approaches and try to integrate them into an Angular form as an example.

Notice: All code samples are available on Github!
Asynchrone Operationen: Blazor WebAssembly für Angular-Entwickler – Teil 5 [Screencast]

Asynchrone Operationen: Blazor WebAssembly für Angular-Entwickler – Teil 5 [Screencast]

Eine Webanwendung will natürlich auch mit Daten gefüttert werden. Doch diese müssen irgendwo her kommen. Nichts liegt näher als diese von einer Web API zu laden. Dieser Screencast zeigt, wie asynchrone Operationen in Blazor funktionieren und welche gravierenden Unterschiede es zu Angular gibt.
Typings: Blazor WebAssembly für Angular-Entwickler – Teil 4 [Screencast]

Typings: Blazor WebAssembly für Angular-Entwickler – Teil 4 [Screencast]

C# und TypeScript entstammen der Feder der selben Person. Doch sind sie deshalb auch gleich? In diesem Teil der Screencast-Serie erfahren Sie, wie mit Typen in den beiden Programmiersprachen verfahren wird und welche Unterschiede es gibt.
Bindings: Blazor WebAssembly für Angular-Entwickler – Teil 3 [Screencast]

Bindings: Blazor WebAssembly für Angular-Entwickler – Teil 3 [Screencast]

Wer Komponenten einsetzt, steht früher oder später vor der Fragestellung, wie man Daten an die Komponente übergibt oder auf Ereignisse einer Komponente reagiert. In diesem Screencast wird gezeigt wie Bindings bei Komponenten funktionieren, also wie eine Komponente Daten von außerhalb benutzen und Rückmeldung bei Aktionen geben kann.
Komponenten: Blazor WebAssembly für Angular-Entwickler – Teil 2 [Screencast]

Komponenten: Blazor WebAssembly für Angular-Entwickler – Teil 2 [Screencast]

Bei der Entwicklung einer Webapplikation kommt es ständig vor, dass UI-Teile immer und immer wieder verwendet werden. Damit nicht immer Copy & Paste verwendet werden muss, können diese Teile in Komponenten zusammengefasst werden.