Avoiding the dreaded Pyramid of Doom in Angular by utilizing rxjs .subscribe to reduce the number of nested .subscribes

I've been diving into the wonders of RxJS's .merge lately, but I thought I'd throw my question out here too because the explanations are usually top-notch.

So, here's the scenario: I have a form that triggers a modal window based on user input. I listen for the modal close event, pass some data back, then make a service call to fetch more data. After that, I do it all over again with another service method to update some info, followed by running a local method. Essentially, I've got a chain of 3 nested .subscribes going on.

const dialogRef = this.matDialog.open(ModalWindowComponent, {});
let userId = 4;
let userData = {};

// dialog is closed
dialogRef.afterClosed().subscribe((result) => {
  if (typeof result === 'string') {
     // get some data from a service
     this.userService.getUser(userId).subscribe((user: any) => {
        let mergedObj = Object.assign({}, user, {newProperty: result});
          // update the data using another service
          this.scbasService.updateUser(userId, mergedObj).subscribe(() => {
             this.doSomethingElse(userData); 
      });
    });
  }
});

This structure is often referred to as the "pyramid of doom". In AngularJS days with promises, we used chained .then()s to avoid this. How can I flatten out my code here to reduce excessive indentation?

If you have any suggestions or need further clarification, feel free to let me know so I can refine my question.

Answer №1

If you're looking to enhance your code, consider implementing the following approach:

dialogRef
  .afterClosed()
  .filter(result => typeof result === 'string')
  .mergeMap(result => this.userService
    .getUser(userId)
    .mergeMap(user => {
      let updatedUser = Object.assign({}, user, { newProperty: result });
      return this.scbasService.updateUser(userId, updatedUser);
    })
  )
  .do(() => this.doSomethingElse(userData))
  .subscribe();
  • Utilize filter to process only string results.
  • Compose an inner observable for both getUser and updateUser calls using mergeMap.
  • Merge the inner observable into the outer one with another mergeMap function.
  • Perform additional actions after updating the user with do.
  • Don't forget to call subscribe at the end to execute everything.

Remember that nesting subscribe calls within each other is considered a coding antipattern.

To streamline the code further, you can use the result selector in the initial mergeMap to add the property directly:

dialogRef
  .afterClosed()
  .filter(result => typeof result === 'string')
  .mergeMap(
    result => this.userService.getUser(userId),
    (result, user) => Object.assign({}, user, { newProperty: result })
  )
  .mergeMap(
    userWithNewProp => this.scbasService.updateUser(userId, userWithNewProp)
  )
  .do(() => this.doSomethingElse(userData))
  .subscribe();

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Creating routes for specific named router outlets within a subcomponent involves defining the routes in a way that targets those

I am eager to create an Angular 4 application with two distinct sub-apps: main-app and admin-app. My initial idea is to have an app component that is bootstrapped and contains only the <router-outlet> in its template: app.component template: <r ...

Angular open component in a new tab without initializing the entire application

One challenge I'm facing in my Angular 10 application is opening a component in a new browser tab without having to bootstrap the entire app again and create another instance. I need a solution that doesn't involve creating multiple app instances ...

Angular allows for the easy population of an array into an input box

I've been given a task to create 10 different input forms and populate them with an array of 10 users using Angular's looping functionality. By the end of this task, I expect to have 10 input forms filled with various user data from the array. ...

Running multiple function blocks disables the construction of certain projects while the 'watch' flag is in use

In an Angular environment and using NX, I have set up the following script in my package.json for building the project: "start:all": "nx run-many --target=build --projects=\"proj1,proj2,proj3,proj4\" --watch --skip-nx-cac ...

Creating a component in the libs using Angular schematics with Webstorm

After setting up an angular project in nx monorepo, I encountered a problem when creating UI libraries and attempting to utilize the "Angular Schematic" feature. Despite following the steps as shown in the provided image, the generated files were still bei ...

TypeScript generic types allow you to create reusable components that

function genericIdentity<T>(arg: T): T { return arg; } let myGenericIdentity: <U>(arg: U) => U = genericIdentity; I see that the 'genericIdentity' function is accepting an argument of a generic type. However, I am unsure about ...

Utilizing functional components and formatter functionality in React with react-jsx-highcharts

Exploring the capabilities of react-jsx-highcharts through a polar chart. Software Versions: React: 17.0.1 react-jsx-highcharts: 4.2.0 typescript: 4.0.3 I opt for functional components in my code, hence no usage of "class" or "this." The snippet f ...

Problem with organizing data by dates

My timers list looks like this: timer 1 => { startDate = 17/01/2019 11PM, endDate = 18/01/2019 9AM } timer 2 => { startDate = 18/01/2019 7AM, endDate = 18/01/2019 1PM } timer 3 => { startDate = 18/01/2019 12PM, endDate = 18/01/2019 10PM } time ...

Encountering a snag when trying to start up the Angular application

When I try to start my Angular application using the command ng serve --port 4200 --host 127.0.0.1 --disable-host-check true --ssl true, I encounter the following problem: Error: Could not locate the '@angular-devkit/build-angular:dev-server' bui ...

Guide on converting SASS into CSS

I am embarking on my journey with Angular 4 and have successfully set up my project using Angular CLI. Now, I find myself wondering about the process of compiling an existing style.sass file into style.css, especially since the CLI utilizes webpack as its ...

Tips for sharing HTML-String between component.ts and component.html files

I need help transferring an HTML format string from the .component.ts file to the .component.html file. In my application, there is a layout folder. The layout.component.ts file contains the following code: import { Component, OnInit } from '@angula ...

Creating a custom Angular Material Stepper form with modular steps implemented as individual components

Angular Material stepper is functioning well in a single component, but now I need to use it in around 10 different components, each with different types of forms. To address this, I have decided to break down the stepper into separate components, with eac ...

Angular CLI build/serve/test commands task problem matcher in VS Code

In an attempt to set up VS code tasks for Angular CLI commands such as 'ng build', 'ng serve', and 'ng test', I want to generate a list of problems that can be easily navigated when running a CLI command. Currently, I execute ...

Top Tip for Preventing Angular Version Compatibility Issues

Here is an illustration that delves into my inquiry ----> Version Conflict The dilemma arises when my product has a dependency on a node package, which in turn relies on a specific version of Angular, denoted as version #y. However, my product itself ...

Using prevState in setState is not allowed by TypeScript

Currently, I am tackling the complexities of learning TypeScipt and have hit a roadblock where TS is preventing me from progressing further. To give some context, I have defined my interfaces as follows: export interface Test { id: number; date: Date; ...

Increase progress by pressing a button

I've implemented the NgCircleProgressModule library to display a circle with a percentage value in the center. It seems that the progress value remains static and only updates when the buttons are clicked. Clicking on the 25% button within the circl ...

Does ESLint have a rule that prohibits the use of hardcoded color values in styled-components?

Assistance is needed to address a specific issue we are facing. We want to ensure that our developers stick to the designated colors in our project. Is there a method to validate the usage of hardcoded strings like #FFFFFF, rgb(255,255,255), rgba(255,255 ...

When launching the Angular SSR app, an uncaught ReferenceError occurs because the document is not defined

After successfully running "npm run dev:ssr" a problem arises when the rendered file shows an error. How can this issue be resolved? ERROR Error: Uncaught (in promise): ReferenceError: document is not defined ReferenceError: document is not defined a ...

Having trouble mocking Node fs Modules using Sinon

I am facing an issue with mocking the promises methods of the node fs module in my code. When my appData.ts file is executed, it calls the actual fs.promises.mkdir method instead of the mock function defined in \__tests__/appData.test.js. I suspect ...

Obtain pictures from MongoDB for a website using Angular6

I'm currently in the process of developing my website and I want to showcase an image from my database on the header. Each customer is assigned a unique logo based on their ID, but I'm unsure how to use Angular to display it. Below is my code s ...