RxJS and Angular: Best Practices

Taira Seal
6 min readSep 11, 2023

RxJS is a powerful library for handling asynchronous data in JavaScript. It can be used to create Observables, which are streams of data that can be subscribed to. Angular is a popular framework for building web applications. It can be used to create reactive applications by using RxJS.

In this post, we will discuss some best practices for using RxJS in Angular applications. These best practices will help you to write more elegant, efficient, and testable code.

Learn how to think reactive

Reactive programming is a different way of thinking about code. It is based on the idea of streams of data, rather than traditional event-driven programming. When you think reactively, you think about how data flows through your application and how you can react to changes in that data.

const numbers = [1, 2, 3, 4, 5];

const observable = from(numbers);

observable.subscribe((number) => console.log(number));

This code creates an Observable from an array of numbers. The Observable then subscribes to a function that logs the numbers to the console.

Use pipeable operators

Pipeable operators are a powerful way to chain together multiple operations on an Observable. This can help you to write more concise and readable code. For example, you could use the map operator to transform the values of an Observable, or the filter operator to filter out unwanted values.

const numbers = [1, 2, 3, 4, 5];

const observable = from(numbers)
.pipe(map((number) => number * 2));

observable.subscribe((number) => console.log(number));

This code uses the map operator to double the values of the Observable.

Use ASCII marble diagrams

ASCII marble diagrams are a helpful way to visualize the behavior of an Observable. This can be useful for debugging and understanding complex code. A marble diagram is a simple way to represent the values emitted by an Observable. Each line in the diagram represents a value, and the type of the value is indicated by its color.

const observable = from([1, 2, 3, 4, 5]);

const marbleDiagram = observable.toMarbleDiagram({
// The default separator is a space.
separator: '-',

// The default colors are black for completed values, red for errors, and
// blue for interrupted values.
colors: {
next: 'green',
completed: 'cyan',
error: 'red',
interrupted: 'blue',
},
});

console.log(marbleDiagram);

This code creates an Observable from an array of numbers and then converts it to an ASCII marble diagram. The marble diagram is customized with a custom separator and colors.

The toMarbleDiagram() method takes an options object as its argument. The options object can be used to customize the appearance of the marble diagram.

The following are the properties of the options object:

  • separator: The separator between the values in the marble diagram. The default value is a space.
  • colors: An object that maps the type of value to a color. The default values are:
  • next: Black
  • completed: Cyan
  • error: Red
  • interrupted: Blue

Use pure functions

Pure functions are functions that do not modify their arguments or have any side effects. Using pure functions can help to prevent memory leaks and make your code more testable. A pure function is a function that always returns the same output for the same input. This means that you can be sure that your code will not have any unexpected side effects.

function double(number) {
return number * 2;
}

const numbers = [1, 2, 3, 4, 5];

const observable = from(numbers)
.pipe(map(double));

observable.subscribe((number) => console.log(number));

This code defines a pure function called double() that doubles its input. The Observable then uses the map() operator to apply the double() function to each value.

Avoid memory leaks

RxJS Observables can leak memory if they are not properly disposed of. To avoid this, make sure to unsubscribe from Observables when you are no longer using them. You can unsubscribe from an Observable by calling its unsubscribe() method.

const observable = from(numbers);

observable.subscribe((number) => console.log(number));

// Unsubscribe from the Observable when we are done with it.
observable.unsubscribe();

This code unsubscribes from the Observable when we are done with it. This prevents the Observable from leaking memory.

Avoid nested subscribes

Nested subscribes can make your code difficult to read and maintain. Instead, try to use chaining or higher-order operators to avoid nesting. Chaining is the process of connecting multiple operators together. Higher-order operators are operators that take other operators as arguments.

const observable1 = from([1, 2, 3, 4, 5]);
const observable2 = from([6, 7, 8, 9, 10]);

const subscription = observable1
.pipe(
map((number) => number * 2),
subscribe((number) => console.log(number)),
)
.pipe(
mergeMap((number) => observable2.pipe(map((number2) => number + number2)),
)
.subscribe((number) => console.log(number));

This code creates two Observables, observable1 and observable2. The first Observable emits the numbers 1 to 5, and the second Observable emits the numbers 6 to 10.

The code then uses the mergeMap() operator to merge the two Observables. The mergeMap() operator takes a function as its argument. The function is called for each value emitted by the first Observable, and it returns an Observable. The values emitted by the returned Observable are then merged with the values emitted by the first Observable.

In this case, the function passed to the mergeMap() operator takes a number emitted by the first Observable and returns an Observable that emits the number plus the corresponding number from the second Observable.

The code then subscribes to the merged Observable. This ensures that all of the values emitted by the two Observables are processed.

This code avoids nested subscribes by using the mergeMap() operator. The mergeMap() operator allows us to combine multiple Observables into a single Observable, which can help to simplify our code and avoid nesting.

Share subscriptions

Sharing subscriptions can help to improve performance by avoiding the creation of duplicate Observables. When you share a subscription, you are essentially saying that multiple Observables can use the same underlying data source. This can be useful for improving performance, especially when you are dealing with large amounts of data.

const observable = from([1, 2, 3, 4, 5]);

const subscription = observable
.pipe(
map((number) => number * 2),
subscribe((number) => console.log(number)),
)
.pipe(share());

const subscription2 = observable
.pipe(
map((number) => number * 3),
subscribe((number) => console.log(number)),
)
.pipe(share());

This code creates an Observable that emits the numbers 1 to 5. The code then uses the share() operator to share the subscription to the Observable. This means that both subscriptions will receive the same values emitted by the Observable.

In this case, the first subscription prints the numbers multiplied by 2, and the second subscription prints the numbers multiplied by 3.

The share() operator is a powerful way to share subscriptions between multiple subscribers. This can help to improve performance, especially when you are dealing with large amounts of data.

Don’t expose subjects

Subjects are a powerful tool, but they can also be abused. Don’t expose subjects to the public API of your components or services, as this can lead to unexpected behavior. A subject is an Observable that can also emit values. This makes it a powerful tool for broadcasting data to multiple subscribers. However, it is important to use subjects carefully, as they can be abused to create unexpected behavior.

A better way to do this is to create a private subject and then expose a function that returns an Observable that is subscribed to the subject. This way, other components can only subscribe to the Observable, and they cannot directly interact with the subject.

Here is an example of how to do this:

const subject = new Subject();

// This is a better way to do it because it exposes a function that returns
// an Observable that is subscribed to the subject.
export function getObservable() {
return subject.pipe(subscribe((number) => console.log(number)));
}

This code creates a private subject and then exports a function called getObservable(). The getObservable() function returns an Observable that is subscribed to the subject. This way, other components can only subscribe to the Observable, and they cannot directly interact with the subject.

Conclusion

Following the best practices will make your code easier to read, understand, and maintain, and it will help you to avoid bugs and errors. Here are some of the advantages of following these best practices:

  • Elegant code: RxJS provides a number of powerful operators that can be used to write concise and elegant code. You can use these operators to your advantage and write code that is easy to read and understand.
  • Efficient code: RxJS can be used to write efficient code by taking advantage of the asynchronous nature of Observables. By following the best practices, you can ensure that your code is using RxJS in the most efficient way possible.
  • Testable code: RxJS provides a number of testing tools that can be used to test your code. You can make your code more testable and ensure that it is working as expected.

--

--

Taira Seal

Frontend developer with 8+ years of experience. Specializing in HTML, CSS, JavaScript, Typescript, Angular. Enjoys traveling the world 🌍 Love crafting! 🎨