Skip to content
Logo Theodo

What are Observables? A 3-step guide to understanding them

Mathuti Sivamanogaran6 min read

An alien and a human coding an app

Observables are one of the things I struggled most with when I first started coding in Angular. With the library RxJS, they are used for managing asynchronous operations, handling events, managing states, and creating dynamic user interfaces. This article will go through some of the many thoughts I had when starting with Angular, to give a new perspective and a better understanding of what Observables are and how to best think when using them. And for that, what’s better than trying to build an app to navigate space and its many stars? (Nothing)

📺 Level 0: Don’t forget to subscribe! (to the observable)

One example of how an observable is used is to handle HTTP requests.

My alien best friend and I want to be able to travel in space, more specifically to go see all the many constellations that cover the Earth’s sky. To do that, we would like to make a very simple app. The thing is, we only know the most famous stars, like Sirius, and not the constellations they belong to. Thus, we need to make an HTTP call to an API to retrieve the constellation: ConstellationHttpCall, that takes a star name as parameter.

let constellation: string;

constellation = ConstellationHttpCall('Sirius')

console.log(constellation)
//*Observable {source: Observable, operator: ƒ}*

This doesn’t seem to work. Why? Just like a promise that was not resolved, this does not directly give us the value we need. ConstellationHttpCall('Sirius') is an observable. We need to subscribe to it. Subscribing to the observable is like turning on your receiver to capture the transmissions coming from a spaceship.

Most web developers have had experience with using promises to deal with HTTP calls, but careful, they are not exactly the same as an observable. A promise represents a single future value, like receiving a package delivery. You receive the value once, while an observable represents a stream of values over time, akin to continuously receiving messages. Of course, an observable can also only send one value over time, but you still need to subscribe to it:

ConstellationHttpCall('Sirius').subscribe(searchResult => {
  constellation = searchResult;
});

console.log(constellation)
// Canis Major

In that case, turning the observable into a promise might be a good idea… But let’s not get hasty!

🔄 Level 1: Observables, asynchronism & reactive programming

Now that we understand what an observable is, let’s move along. My friend and I need to know how many stars we should visit while discovering Sirius’ constellation. We’ll need another HTTP call to do that.

let constellationStarNumber: number;

ConstellationHttpCall('Sirius').subscribe(searchResult =>
  constellation = searchResult
);

ConstellationInfoHttpCall(constellation).subscribe(constellationSearchResult =>
  constellationStarNumber = constellationSearchResult.starNumber
);

console.log(constellationStarNumber);
//undefined

Oh no, the number of stars in the constellation is undefined! What happened?

The above code may seem logical at first glance, but it encounters a fundamental problem: asynchronism. Asynchronous operations like HTTP requests do not block the execution of subsequent code. Therefore, by the time the second HTTP call is made, the value of constellation may not be available yet.

The way this code is written is based upon the assumption that the code is executed line by line, with a sequential execution of commands, which follows the imperative programming paradigm. In reactive programming, which is what observables and RxJS are based on, events act like ongoing events that we can keep an eye on. We’re able to tweak them, blend them together, and create brand-new streams of data. They work asynchronously: their effects might not be obvious to the untrained eye. We need to combine our two calls.

🧪 Level 2: How to combine two observables?

Now we know that we have to combine our observables. Also, my friend and I want information about more constellations than just Sirius, which is why we’re adding a search input in our web application, that’ll look for constellation info for any star.

My friend and I have had the most brilliant idea to join our observables:

let starName: string; // The parameter for our HTTP call

ConstellationHttpCall(starName).subscribe(searchResult => {
  ConstellationInfoHttpCall(searchResult).subscribe(constellationSearchResult => {
    constellationStarNumber = constellationSearchResult.starNumber;
  });
});

This does work, but the shaman of our village says that if we use this to navigate in space, we might be in great danger!

Why? What we’re doing here is triggering a new subscription for every value emitted by the observable ConstellationHttpCall(starName). You cannot unsubscribe from the nested observables. If they are not managed properly, this can lead to a proliferation of unhandled observables and subscriptions, all lingering in memory, which causes memory leaks and therefore negatively impact performances (and impact our trip to space!).

Thus, while it’s essential to combine observables to obtain our desired information, it’s equally crucial to do so with the right operators. Think of them as functions that can be used to transform the data streams produced by observables. Using operators allows us to elegantly handle asynchronous operations and manage the flow of data without falling into the pitfalls of nested subscriptions. Popular operators are for instance switchMap, mergeMap or concatMap.

Here we will use switchMap().

ConstellationHttpCall(starName).pipe(
  switchMap(constellation => ConstellationInfoHttpCall(constellation))
).subscribe(constellationSearchResult => {
  constellationStarNumber = constellationSearchResult.starNumber;
});

The pipe() function is needed to chain multiple operators together.

Conclusion

From Level 0’s foundational understanding of subscribing to observables to Level 1’s realization of the asynchronous nature of HTTP calls, and finally, to Level 2’s quest for combining observables efficiently, we’ve encountered challenges and triumphs similar to navigating the stars themselves. Handling observables is tricky, but when keeping in mind the right logic and tools, it can become as easy as sailing in the Milky Way.

Liked this article?