Tips for escaping the RxJS subscribe loop of doom?

Currently, I am utilizing Angular's RxJs subscribe feature to initiate an HttpClient call and then proceed to make another call using the data retrieved from the initial request. Specifically, I first retrieve an address object and subsequently use that object to make a further API call. Here is a simplified example:

@Injectable()
export class AddressService {
  constructor(private http: HttpClient) { }

  getById(addressId: string, userId: string) {
    return this.http.get(BACKEND_URL + 'getAddressById/' + [addressId, userId]);
  }
}
  
export class AddressModalComponent implements OnInit {
  constructor(private alertService: AlertService, private addressService: AddressService,           @Inject(MAT_DIALOG_DATA) public data: any, private dropdownService: DropdownService)

  ngOnInit() {
    this.addressService.getById(this.data.id, this.data.userId)
        .subscribe(
          (address: Address) => {
            this.dropdownService.getCidadesBrByEstado(address.name)
              .subscribe((cities: BrCity[]) => {
                this.cities = cities;
                this.address = address;
              },
              error => console.log(error));
          }, error => { this.alertService.error(error);
          }
        );
    }
  }
}

I am striving to streamline my code by avoiding multiple subscribes, as there are numerous instances like this in my project. Ideally, I would prefer an Async/Await approach akin to Node.js promises while still leveraging Observables on the component level. My understanding of RxJs commands is not extensive... Could someone suggest a more optimized way to handle multiple API calls with just one subscribe and catch block?

Answer №1

Here is an example code snippet for you:

import { zip, concatMap } from 'rxjs/operators'

this.userService.getUserById(this.id).pipe(
  concatMap(user => this.postService.getPostsByUser(user.name).pipe(
    // Combining user and posts data into one object
    map(posts => ({ user, posts }))
  ))
).subscribe(({ user, posts }) => {
  this.user = user
  this.posts = posts
})

Answer №2

When working with RxJS in Angular, it is recommended to utilize the Observable Class instead of traditional callbacks. To tackle callback hell within RxJS, you can make use of Observable's Operators API such as switchMap() method (and other methods like map(), concatMap(), etc). Below is an example demonstrating the use of switchMap() method:

const serviceA(params): Observable<any>;
const serviceB(params): Observable<any>;
const serviceC(params): Observable<any>;

serviceA(paramsA).subscribe(
    serviceAResult => {
        serviceB(paramsB).subscribe(
            serviceBResult => {
                serviceC(params).subscribe(
                    serviceCResult => {
                        // logic code here. Avoiding subscription hell!
                    }
                )
            }
        )
    }
)

To improve code structure, we can optimize using switchMap() method:

const serviceB$ = serviceA(paramsA).pipe(
    switchMap(serviceAResult => {
        return serviceB(paramsB);
    })
);

const serviceC$ = serviceB$.pipe(
    switchMap(serviceBResult => {
        return serviceC(paramsC);
    })
);

serviceC$.subscribe(
    serviceCResult => {
        // logic code goes here.
    },
    error =>{
        // handle errors
    }
);

For more insights on handling callback hell, check out this informative resource: callback hell post.

Answer №3

If you're not particularly interested in streams, another approach would be to convert the Observables into promises and utilize async/await as shown below:

async ngOnInit(): Promise<void> {
  this.address = await this.addressService.getById(this.data.id, this.data.userId).toPromise();
  this.cities = await this.dropdownService.getCidadesBrByEstado(this.address.name).toPromise();
}

Don't forget to handle any potential errors using try catch.

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

What is the proper way to import and define typings for node libraries in TypeScript?

I am currently developing a node package in TypeScript that utilizes standard node libraries such as fs, path, stream, and http. Whenever I attempt to import these libraries in a .ts file, VS Code flags the line with an error message: [ts] Cannot find m ...

Obtain multiple class instances through HTTP-Get in Angular

Initially, explaining this with my actual code can be confusing, so I'll simplify the issue using a smaller example. Imagine my project retrieves data from 2 tables on the server, employeeDetails and employeeNames. employeeNames: This table consists ...

What is the process in TypeScript for defining a custom variation of a generic function?

Suppose we have a generic function: const f1 = <T>(x: T) => console.log(x) We can then create a specialized version for f1, like this: const f2 = (x: number) => f1(x) If we try to call f2 with an argument of type string, TypeScript will thr ...

Troubleshooting Issue with Angular Property Binding for Image Dimensions

Currently, I am exploring property binding in Angular through an example. The idea is to use an image and bind its width, height, and src attributes using property binding. Surprisingly, even though there are no errors and the image renders successfully vi ...

Typescript error message TS2693: The identifier 'Note' is used as a value although it refers to a type

I have been attempting to incorporate Typescript into my Firebase project, but unfortunately I am encountering the following errors: error TS2693: 'Note' only refers to a type, but is being used as a value here. The code snippet I used is as fo ...

Navigating from a library to app components: A step-by-step guide

I successfully converted my login-component into a library, and now the first thing displayed in myApp is the login page. However, I'm facing an issue with navigating to the home page after a successful login. Can anyone provide guidance on how I can ...

Is there a way to determine when an animation finishes in Vue Nativescript?

Vue Nativescript does not support the v-on hook, even though I found a solution for VueJS. Check out the solution here Currently, I am applying a class to trigger an animation: Template <Image ref="Image" :class="{scaleOut: scaleOutIsActive}" ...

Setting a timeout from the frontend in the AWS apigClient can be accomplished by adjusting the

I am currently integrating the Amazon API Client Gateway into my project and I have successfully set up all the necessary requests and responses. Now, I am trying to implement a timeout feature by adding the following code snippet: apigClient.me ...

Designing a Tombstone with TypeScript

In TypeScript, I have certain values that are only necessary within the type system and not at runtime. I am seeking a way to void these values without losing their type information. The function bury illustrated below is intended to replace the actual v ...

Show a stylish container when the server encounters an HTTP error with status code 500

My approach for error handling involves a straightforward ErrorHandler class structured like this: export class AppErrorHandler implements ErrorHandler { handleError(error : any){ //console.log('test error ', error.status); ...

Refresh the array using Composition API

Currently, I am working on a project that utilizes Vue along with Pinia store. export default { setup() { let rows: Row[] = store.history.rows; } } Everything is functioning properly at the moment, but there is a specific scenario where I need to ...

Objects That Are Interconnected in Typescript

I have been delving into TS and Angular. I initially attempted to update my parent component array with an EventEmitter call. However, I later realized that this was unnecessary because my parent array is linked to my component variables (or so I believe, ...

The new Angular 2 router in RC1 seems to have a glitch where the second level router-outlet only functions properly the

Check out this plunker example: http://embed.plnkr.co/B8feYX7e6oxvI2u4zmYW/ If you click on "Child 1", it works. However, clicking on "Child 2" does not work. Once you click "HOME", then clicking on "Child 2" works but "Child 1" does not. It seems the na ...

Is there a way to directly increment a variable in an Angular 4 template binding?

Presented here is an object that I am working with: this.people = [{ name: 'Douglas Pace', title: 'Parcelivery Nailed The Efficiency', content: 'Since I installed this app, its always help me book every ...

The string is being added to an array twice

I am managing two sets of lists where strings will be transferred between them. One set contains a list of strings for searching purposes. The other set contains the same list of strings but is not used as a filter. The second set functions in a similar ...

Utilizing *ngIf for Showing Elements Once Data is Completely Loaded

While working on my Angular 2 app, I encountered an issue with the pagination UI loading before the data arrives. This causes a visual glitch where the pagination components initially appear at the top of the page and then shift to the bottom once the data ...

A spread operator is required to have a tuple type or be assigned to a rest parameter within a React component

I'm currently working with Typescript and have the following code snippet: const [history, setHistory] = useState([Array(9).fill(null)]); const newHistory = history.slice(0, currentStep + 1); When attempting to set a new State using spread operators ...

Best Practices for TypeScript and React: How to Handle Component State for Mounted Components

One technique to avoid calling .setState() on an unmounted component is by using a private property like _isMounted to keep track of it, as discussed in a blog post. I have implemented this method as follows: class Hello extends React.PureComponent{ _isM ...

Transforming a sizeable Typescript undertaking into ES6 modules

I am faced with the challenge of transitioning a fairly large app (~650 files) that currently has a mix of ES6 modules and legacy global namespace internal modules and classes. My goal is to convert everything to 100% ES6 modules. Considering an Iterativ ...

Mongoose and TypeScript - the _id being returned seems to be in an unfamiliar format

Experiencing unusual results when querying MongoDB (via Mongoose) from TypeScript. Defined the following two interfaces: import { Document, Types } from "mongoose"; export interface IModule extends Document { _id: Types.ObjectId; name: stri ...