Clean code through Reactive programming in Angular with RxJS

Last Update : 8 February 2021

One of the most common hindrances a developer will cope within its journey will be a bug due to a side effect of an asynchronous method. Everything was supposed to work as expected, though, eventually, it did not. Using imperative programming, developers often pull their hair out anticipating when a function will be triggered and how it will affect the state. From this chaos will soar multiple bugs. Reactive programming, therefore, appeared as an answer for developers to put them in a state of mind where they can code imitating the events that occur in their application.

Though shifting from imperative to reactive programming is tricky at first, in this article we will define what coding reactively actually means, the tools to code reactively and through an example show in which pitfalls a greenhorn reactive programmer could fall and how to avoid it. In this example, our purpose is to build a cocktail carousel app.

We aim to create:

  • a search bar where users can type in a text, to look for a drink they want to discover
  • we display a list of 3 answers on the screen
  • a list that updates every 1.5 seconds
  • images switching one spot to the left, as a new one appears on the right-hand side

Ideally this would look like this

Gif1

But before we need to change our focus...

As the owner of a store during the pandemic, you aim at counting the number of people that are currently in your store in order to enable social distancing inside. You could think of an iterative way which would be to check the number of people in your store and count it manually. You could also start at 0 in the morning when you open and then only look at the door, increment or decrement based on people coming or leaving the place. The first option requires burning the midnight oil, especially if your store has a lot of rooms. With the second option, you only have to look at the door without moving from your chair, which makes the calculation way easier.

This is exactly what Reactive programming is about:

"Reactive programming is a programming paradigm that is built around the notion of continuous time-varying values and propagation of change. It facilitates the declarative development of event-driven applications by allowing developers to express programs in terms of what to do, and let the language automatically manage when to do it." Bainomugisha et al., A Survey on Reactive Programming, 2012

Though it's conceptually hard to dive in at first glance, the code becomes easier to read and maintain, with a paradigm based on events happening in your application. Indeed, to come back to our store example, the second method fits clients' behavior best. A client spending a long time wandering in your store could be counted in many of the checks you make. Looking at the door is independent of this behavior. No matter what the client does in your store, it will only trigger two kinds of events: entrance, exit.

Developers start thinking about code as a suite of events that modify a state. Rather than looking at the state, the object itself, the developer would look at the events that would modify the state, regardless of the state itself.

Here we'll dive in more depth into how Angular implements Reactive programming with the use of a pattern called Observable.

From callbacks to streams

To better understand what Observables are, let's try to differentiate them starting from one of the most famous asynchronous patterns in JS, Promises. While a Promise happens once in time and will return the desired output or throw an error, Observables in Angular, are a stream of data over time. A stream can have multiple values succeeding at various frequencies in time. After we get the first value emitted on the Observable stream, contrary to a Promise, it will keep living until you complete it.

"Reactive Programming is programming with asynchronous data streams."

In order to access and use the values that are emitted through this stream, you can subscribe to this Observable stream and get notified every time a new value is emitted on this stream.

Observable([1, 2, 3]).subscribe(number => console.log(number))
//Output: 1, 2, 3

Observable([1, 2, wait3seconds, 3]).subscribe(number => console.log(number))
//Output: 1, 2, ..., 3

While Promises are like the action of going to a shop and buy your newspapers once, Observables act like a subscription to the magazine. Every time a new edition is published, every subscriber will receive a copy. Both the magazine (in case of bankruptcy for instance) or the subscribers can stop the subscription to this stream.

Operators

We are now going to dive into how to create and manipulate (transform, filter, ...) and terminate these observables. To perform these actions one can use Operators (for creation, and also transformation, filtering, termination). To better understand what these operators do to an Observable, we represent them using marble diagrams. You'll find a list of operators and their diagrams here at RxMarbles. These operators were popularised by the project ReactiveX which aims to provide different APIs in multiple programming languages (including RxJS, the one used in this article). ReactiveX implements the Observer pattern, which is having Observers subscribing to events (Observables) and performing side effects based on this event, using combinations of operators.

I can subscribe to a magazine. When I receive a new one, I use an operator to make coffee and another one to read it.

Let's look at a classic creation operator to better grasp how observables are generated and evolve through time: the interval. Interval creates a stream of data that emits an incremented suite of integers at a given pace (in this example every 1000 ms)

Img2

This creation can be pretty useful, but let's introduce transformation operators with another use case. If we don't want the list of integers but rather the list of multiples of ten we can use the Observable we just created and map the values using a transformation operator. This will enable you to transform the data emitted from the stream of the observable and create another stream from the input one.

Img3

As a matter of fact, we can look at the map operator which applies a function to an entry Observable stream, and returns an Observable whose values are the output of the function applied to the entry stream values.

Eventually, you can use operators to stop the Observable stream. For instance, the first operator will complete the Observable after the first value is emitted on the stream. The first operator is actually enough to make an Observable behave like a Promise (which is the opposite of the purpose of this article but interesting to grasp the concept of Observables).

Img4

Let's look at how we could use Observables, using them as Promises (which is the main pitfall new reactive programmers fall into).

Moving to reactive programming: (unwisely) use Observables like Promises

The first misuse of Observables developers can make is to use them as if they were Promises thanks to the subscribe method. This method looks like a callback in which developers tend to put the behavior they will expect. Indeed one of the main pain point at first when dealing with Observables is how to manipulate the value. As Observables are lazy by nature, Angular will not calculate an observable if you do not subscribe to it, hence the reflex to subscribe, get the value and do something with it. This is exactly what callbacks are about.

However, as we said, unlike a Promise, Observable live through time, and contrary to callbacks, the code contained in the subscribe method will be executed every time the observable changes, resulting in potential side effects and memory leaks potentially.

Let's now look at a first piece of code that generates an (invalid) implementation of the carousel

The carousel dynamic is created thanks to an observable interval. This observable emits every 1500ms an integer. Its marble diagram is the aforementioned one.

constructor(private http: HttpClient) {}
search = new FormControl('');
drinksCarousel = [];
baseUrl = '<https://www.thecocktaildb.com/api/json/v1/1/search.php?s=>';
ngOnInit() {
this.search.valueChanges.subscribe((searchText: string) =>
this.http.get<any>(this.baseUrl + searchText).subscribe((results) => {
interval(1500).subscribe((int) => {
this.drinksCarousel = results.drinks.slice(int, int + 3);
});
})
);
}

We can see a first code smell here: nested subscribes. This behavior is often used to mimic callbacks as we said before but will trigger some side effects. If we type several letters, to look for "margarita" we get the following update

<div>
<mat-form-field class="form-field" appearance="outline">
<mat-label> What cocktail are you looking for? </mat-label>
<input matInput [formControl]="search" autocomplete="off" />
</mat-form-field>
</div>

<div *ngFor="let drink of drinksCarousel" class="horizontal">
<app-cocktail-view
[img]="drink.strDrinkThumb"
[name]="drink.strDrink"
[glass]="drink.strGlass"
[instructions]="drink.strInstructions"
></app-cocktail-view>
</div>

Gif5

What we see here is that, instead of updating the drinksCarousel attribute and removing the previous list of values we had with the new one freshly fetched, multiple observables coexist and affect the state of our component simultaneously.

To come back to our analogy of the magazine subscription, what we would like to perform is to change the magazine we subscribe to, however, with nested subscribes, our code is creating a subscription to this new magazine, without terminating the previous one.

Every time our source observable (the search valueChanges) changes, the code inside the subscription is executed again. A new HTTP response is received, a new interval stream is created, thus creating another state change every 1.5s. Indeed none of the subscriptions are canceled, and they stack up silently into a huge mess until your computer crashes.

Thinking Reactively

We saw which drawbacks of imitating the behavior of a Promise to create our Carousel. We used our attribute drinksCarousel to store our images just like in our shop example when the owner was counting the number of people in its store.

We need to change the way we implement this drinksCarousel list of elements. We have 3 events here at stake to create this carousel:

  • The user input in the form
  • The HTTP response to the cocktail API
  • Time (frequency of carousel update)

Therefore to represent those events we will create 3 observables that will act on the state of our component which will be our drinksCarousel. However, from the initial trigger, which is the user input to the carousel, we need to implement the logic between all these events to create a pipe from the user input to the carousel. Using the motto "Divide and rule", we will create smaller pipes that will, in the end, form the final pipe that we're looking for.

Note that the 2 events, user input, and HTTP response are directly correlated as the HTTP call is triggered by the user filling the form. We can therefore start by writing this condition before diving more into the reactive implementation

drinks$: Observable<Drinks[]> = this.search.valueChanges.pipe(
debounceTime(500),
switchMap((searchText) => this.http.get<any>(this.baseUrl + searchText)),
pluck('drinks'),
);

We use here 3 operators: debounce, to wait for the user to stop typing his cocktail request, switchMap to perform the HTTP request, and pluck to extract drinks from the body of the response. Here is the marble diagram of the switchMap operator to grasp better what it is performing and how this is helpful in our case. Look closely at how many times the number 30 appears in the output Observable.

Img6

It will remove the previous search value observable the user wrote in the form, to replace it with a new one.

Here as we only manipulate observables and do not subscribe to it yet, we do not have any side effects, as here we are not creating new subscriptions to a magazine, but simply creating a new instance of the same magazine, that will be sent to our subscribers.

Now that we linked our 2 first events, Our next step is to link the results of the query, the drinks, and time to our state of drinkCarousel. The main part of the work here is to tell Angular that when our component receives a new HTTP response, the previous Carousel needs to be destroyed, the interval timer restarted to 0 and rerendered.

Basically to tell Angular to update the state when a new HTTP response is received, or when 1500ms have passed: we can simply write it this way. Every time the drinks$ observable changes, we create a new interval that will replace the previous one (thanks to switchMap)

triggeredInterval$ = this.drinks$.pipe(switchMap(() => interval(1500)));

Eventually, we just have to implement the logic that our drinkCarousel needs to update, using the list of drinks from the HTTP response and the time event. Fortunately, the RxJS library provides an operator to help us perform this task gracefully.

Img7

We can combine streams of values using the operator combineLatest. It will group the 2 latest values emitted on each stream into a merged one.

drinksCarousel$ = combineLatest([this.drinks$, this.triggeredInterval$]).pipe(    
map(([drinks, int]) => {
return drinks.slice(int, int + 3);
})
);

Gif8

Subscription

You have noticed that we have not yet subscribed to our Observable, while we said previously that Angular won't calculate it in our component if we do not do so. We basically created everything for our magazine, but no one has made a move to subscribe to it yet (therefore Angular is lean enough to not print copies of it). However Angular comes with the very handy async pipe which enables to subscribe and unsubscribe to an observable directly in your template.

<div>
<mat-form-field class="form-field" appearance="outline">
<mat-label> What cocktail are you looking for? </mat-label>
<input matInput [formControl]="search" autocomplete="off" />
</mat-form-field>
</div>

<div *ngFor="let drink of drinksCarousel$ | async" class="horizontal">
<app-cocktail-view
[img]="drink.strDrinkThumb"
[name]="drink.strDrink"
[glass]="drink.strGlass"
[instructions]="drink.strInstructions"
></app-cocktail-view>
</div>

This enables one to kill two birds with one stone, as on the one hand, the async pipe will automatically terminate the subscription when the component is destroyed. On the other hand, most undesired side effects will happen in the subscribe method, as it will be called every time the observable value changes. Removing this subscribe method, and exchanging nested subscribe for pipes will prevent you from coping with multiple side effects, which into the bargain are often hard to understand and debug.

Conclusion

Reactive programming has become a rising field of interest for programmers as it helps you write code the way your application behaves. With the arrival of RxJS for frontend JS frameworks or the soar of Serverless, and Event-driven architectures like CQRS, Reactive programming is becoming more and more relevant and easy to learn and implement for developers.

It still has to cope with several challenges such as:

  • The entry cost for a developer from imperative to reactive
  • Debugging tools are not yet a paragon of fluid dev experience
  • The community has yet to define clearly what Reactive means. The Reactive Manifesto being the most famous one.

How to think reactively and divide our task into sub-tasks that are "link events to state" will help you reduce bugs, and maintain a comprehensible codebase if it is done properly.

If you want a more advanced implementation of this logic I would highly recommend this article that implements quite the same thinking and coding mechanisms.

If you want to discover an elegant way to test these Observables in your components, you can also check out this article