Avoiding Overuse of RxJS in Angular: Using Async/Await and Promises

Md. Saddam Hossain
4 min readFeb 16, 2024

--

In this blog, we will discuss a common mistake in Angular development — the overuse of RxJS for simple every day HTTP operations. If you have ever found yourself writing complex code using RxJS for such operations, then this blog is for you. We will explore a better alternative that can simplify your code and make it more maintainable.

The Problem with Overusing RxJS

RxJS has become a popular tool in Angular development, but there’s a growing sentiment that it’s being overused in cases where simpler solutions exist. This aligns with my own experience, where RxJS has sometimes felt unnecessarily complex for everyday tasks.

Let’s Consider an Example: Complex Backend Save Operation

Let’s consider a scenario where we have a component that needs to perform a complex backend save operation. The component requests lessons, a course, and a user from the user, and then wants to save this data in the backend. The desired sequence is as follows:

  1. Save all the lessons
  2. Save the course using lessons
  3. Save the user using data from the previous steps

The Overused RxJS Solution

Many developers might have written code using RxJS to solve this problem. Here is an example of how the code might look:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, concatMap, finalize, map } from 'rxjs/operators';
import { Lesson } from './lesson.model';
import { Course } from './course.model';
import { User } from './user.model';

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

constructor(private http: HttpClient) { }

// First make a request to save the lessons
complexSaveOperation(newLessons: Lesson[], newCourse: Course, newUser: User) {
// Activate loading indicator
this.loading.loadingOn();

return this.http.post<Lesson[]>('/api/lessons', { lessons: newLessons }).pipe(
// Get back the result of the first request, then save the couse
concatMap(lessons => {
return this.http.post<Course[]>('/api/courses', { course: newCourse, lessons }).pipe(
// Send borth result of the first and second save up the chain
map(course => [course, lessons] as [Course, Lesson[]])
);
}),
// Get back the result of the first 2 request, then save the user
concatMap(([course, lessons]) => {
return this.http.post<User[]>('/api/users', { user: newUser, course, lessons }).pipe(
// Handle errors
catchError(err => {
console.error(err);
const message = 'Error saving data';
this.message.showError(message);
return throwError(() => new Error(message));
}),
// Deactivate loading indicator
finalize(() => this.loading.loadingOff())
);
})
).subscribe();
}
}

This code uses various RxJS operators like concatMap, catchError, and finalize to handle the HTTP requests and manage error handling and loading indicators. While this approach works, it introduces unnecessary complexity and can be challenging for new developers to understand.

The Better Alternative: Using Async/Await and Promises

Thankfully, there is a more straightforward alternative to using RxJS for everyday HTTP operations. By leveraging async/await and Promises, we can achieve the same functionality in a much more readable and maintainable way.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { Lesson } from './lesson.model';
import { Course } from './course.model';
import { User } from './user.model';

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

constructor(private http: HttpClient) { }

async complexSaveOperation(newLessons: Lesson[], newCourse: Course, newUser: User) {

try {
// Activate loading indicator
this.loading.loadingOn();

// Save lessons
const savedLessons = await firstValueFrom(this.http.post<Lesson[]>('/api/lessons', { lessons: newLessons }));

// Save the course with saved lessons
const savedCourse = await firstValueFrom(this.http.post<Course[]>('/api/courses', { course: newCourse, lessons: savedLessons }));

// Save the user with the saved course and lessons
await firstValueFrom(this.http.post<User[]>('/api/users', { user: newUser, course: savedCourse, lessons: savedLessons }));
} catch (err) {
console.error(err);
const message = 'Error saving data';
this.message.showError(message);
throw new Error(message);
}
finally {
// Deactivate loading indicator
this.loading.loadingOff();
}
}
}

The code above demonstrates how we can rewrite the complex RxJS chain using async/await and Promises. The resulting code is much easier to understand and maintain. It follows a linear flow and eliminates the need for multiple levels of nesting.

Benefits of the Improved Solution

Using async/await and Promises for every day HTTP operations in Angular offers several benefits:

  • Simplicity: The code becomes much simpler and easier to understand for developers of all levels.
  • Maintainability: Maintaining the code becomes effortless due to its straightforward nature.
  • Conditional Logic: Adding conditional logic becomes trivial, unlike the complex RxJS solution.
  • Compatibility: You can still use Angular’s HTTP client while converting observables to promises.

Conclusion

As Angular developers, we need to prepare for a future where RxJS becomes optional and less frequently used. While RxJS still has its place for specific scenarios, for every day HTTP operations, using simple async/await and Promises can provide a more maintainable and understandable solution. We hope this blog has highlighted the benefits of avoiding the overuse of RxJS in such situations.

If you found this blog helpful, please leave a like and share your thoughts in the comments. We welcome your suggestions for more content on the Angular Diversity YouTube channel. Thank you for reading!

--

--