RxJS Observables for Angular Programming: What Problem Do They Solve?

Jan 29, 2021


RxJS Observables and all the reactive code can look non-intuitive at the first look. But, Angular uses RxJS Observables as part of it's core features and also as part of it's HTTP and Forms APIs. This post offers a jargon-free answer to the following questions with respect to the use of RxJS observables for Angular UI programming:

  • What problems do RxJS Observables solve?
  • What are they good for & why are they used?
  • When to use them?

Let's Understand Some Concepts

Before looking at some RxJS code, let us understand some related concepts:

Reactive UI Programming & RxJS

UI programming is inherently asynchronous. A UI waits for the user action and performs a task in reaction to that action. As a result, a reactive framework that handles user actions asynchronously is a better natural fit than an imperative UI framework. This benefit may not be evident with a simple "hello world" example. But, when building interactive UIs, reactive programming makes our code manageable, predictable and with fewer bugs.

A switch and a light bulb is a simple real-world analogy to understand reactive programming. A light bulb waits for the change of the switch state. On change of the switch state, it starts or stops emitting light based on the current switch state. A reactive program would wait for the change of switch state and react to it as and when it happens.

A real-world analogy for Reactive programming
A real-world analogy for Reactive programming

RxJS (Reactive Extensions for JavaScript) is a JavaScript library that provides us an API to write reactive code. At it's center are RxJS Observables. Observables provide us with a stream of events & we write our code to react to these events.

RxJS Observables, Streams & Related Concepts

A stream is a sequence of ongoing events ordered in time. For example - a sequence of mouse-pointer positions as the user moves the mouse around the screen. With RxJS Observables, a stream need not only be a sequence of events. It can be a sequence of any data. For example - our social media feed or chunks of response to a video stream request.

The RxJS library provides us with an API to consume these Observables (that is, the data in a stream). We do so by Subscribing to the Observables. Our code that waits for data from the Observables is an Observer. The Observer can perform some operations over the received stream data.

Understanding RxJS Observables, Observers and Subscription
Understanding RxJS Observables, Observers and Subscription

RxJS also provides functions to help us write operations over the received stream data. These are called RxJS Operators. Some RxJS operators also provide the capability to create or combine streams. Or, to pass one stream as input to another (like pipes). Such stacking of streams together can help us build powerful asynchronous experiences with minimal code. Some code examples in the later section demonstrate this capability.

Why Not Promises?

Like Observables, Promises allow us to program for asynchronous events. And, promises are already part of the ES6 JavaScript. In fact, UI programming with many other libraries and framework relies on promises.

But, the Angular team has chosen to leverage RxJS Observables. This means a lot of Angular features are built around Observables. So, to leverage these Angular features, it is ideal to use RxJS Observables.

Also, RxJS Observables offer a super-set of the capabilities available with promises. A promise can deliver only the first value emitted by an asynchronous call. An Observable can emit multiple values. Also, the large number of RxJS operators available to deal with Observables allow us to build tailored streams with a few lines of code. This is not possible with promises.

Let's Look at Some Code

Let's look at some code to understand when and how to use RxJS Observables. In process, we'll try to understand what each line of RxJS Observables code does to gain a better fundamental understanding:

Handling Responses to API Requests with Observables

When programming UIs with Angular, one of the first places we experience RxJS Observables is when consuming API responses. This is because Angular's http library returns an Observable of the type Response (Observable<Response>). As a result, to actually make an HTTP request, we need to subscribe to this Observable. And then, we need to write code to handle the response. Let us try to understand this with an example.

Let us assume we seek to do something with a bunch of to-do's coming from this public test API. To achieve this, let's have a data-structure to represent a single to-do (todo.ts):

export interface ToDo {
    userId: number;
    id: number;
    title: string;
    completed: boolean
  }

And then, we can have code like the following to consume the API:

this.http.get<ToDo[]>('https://jsonplaceholder.typicode.com/todos/')
.pipe(
   map(res => {
      res.map(
         item => {
            //do whatever you like with the to-do item here
            //this is where the code to handle async event of API response goes.
         }
      )};
   )
).subscribe()

Let us try to understand the above snippet. We make an HTTP get call and let know that the response is a ToDo list. As a result, the asynchronous response to the get() call is expected to be of type Observable<ToDo[]>. Now, we apply the RxJS map operator to the response. map allows us to execute the function we provide it for each object in the stream. So, every item within the map operation should essentially be a ToDo instance. We can render this or process it as required. The map operator is encapsulated inside pipe(). RxJS requires this to easily club together multiple pipeable operators (like map operator). Lastly, notice the subscribe() call at the end. With this call, we are subscribing to the stream returned by the pipe() operation which gets http response as a stream.

Here's a working example for the above code-snippet.

So, handling API responses is one of the most common places where RxJS Observables are used effectively.

Building Reactive Inputs with Observables

RxJS Observables enable us to build powerful interactive UI with very little code. Think autocompleting input boxes or validate-as-you-type form controls. Let us try to understand this with an example from Angular's Material UI guide (Stackblitz link). Here's the template HTML from autocomplete-plain-input-example.html from this example:

<form class="example-form">
  <input type="text"
         placeholder="Search for a street"
         [formControl]="control"
         [matAutocomplete]="auto">
  <mat-autocomplete #auto="matAutocomplete">
    <mat-option *ngFor="let street of filteredStreets | async" [value]="street">
      {{street}}
    </mat-option>
  </mat-autocomplete>
</form>

The line [formControl]="control" directive binds the input control in the template HTML to an instance of type FormControl called control. This binding is two-way. Any change in the input element from the UI shall get reflected in the FormControl instance and any change from code made to FormControl instance shall reflect on the UI.

The directive *ngFor="let street of filteredStreets | async" tells the mat-option control that it's values shall come from filteredStreets. The async directive tells that filteredStreets is not a regular list of values but is instead an Observable and the directive async creates a subscription to this observable. As a result of this, rendering of the mat-option is not blocked by availability of the list. Instead, the mat-option gets populated as the values become available from the Observable filteredStreets. Now, let's look at the relevant component code:

filteredStreets: Observable<string[]>;
ngOnInit() {
   this.filteredStreets = this.control.valueChanges.pipe(
      startWith(''),
      map(value => this._filter(value))
   );
}

Now, we know that filteredStreets is an Observable expected to emit a list of strings. this.control.valueChanges is an event that is raised whenever value of the input control changes and it returns an observable. We assign it to this.filteredStreets in the code above. The this._filter(value) filters the original list by the value entered in the input control. The async directive in the template HTML subscribes to this Observable. In this way, the list for mat-option consumes what we type in the input control.

Notice how minimal code allows us to build an interactive UI experience. Also note how the directive async is set within the Angular framework to tell that the specified variable is an RxJS Observable. This tight coupling of asynchronous Angular features built around RxJS Observables makes the use of RxJS Observables necessary to build a smooth asynchronous experience.

Communicating between Components with Observables

RxJS Observables can also enable an elegant interaction between our Angular components. This can typically be achieved with a MessagingService that can hold an Observable:

import { Injectable } from "@angular/core";
import { Observable, Subject } from "rxjs";
@Injectable({ providedIn: "root" })
export class MessagingService {
  private subject = new Subject();
  sendMessage(message: string) {
    this.subject.next(message);
  }
  onMessage(): Observable {
    return this.subject.asObservable();
  }
}

Now, any component can subscribe to the service's Observable. Also, any component can send a message to the service's Observable. By making such a service available within a module or app, one or more components can communicate.

Here is a working example to demonstrate the components communicating via an Observable.

The MessagingService above uses an RxJS Subject. A Subject implements both the Observable and the Observer. With next(), we can pass data into the Observable held by the Subject instance. At the same time, we can also subscribe() to the same instance. The message sending Angular component shall invoke the sendMessage() method and the one listening to the Observable shall invoke onMessage. Below is the example code for a component sending a message:

import { Component } from "@angular/core";
import { FormControl } from "@angular/forms";
import { MessagingService } from "./messaging.service";
@Component({ selector: "sender", templateUrl: "sender.component.html" })
export class SenderComponent {
  control = new FormControl('Sample message to send');
  constructor(private msg: MessagingService) {
  }
  sendMessage(): void {
    this.msg.sendMessage(this.control.value);
  }
}

The code below is for the receiver component. It subscribes to the Observable held within the messaging service's subject through onMessage() method.

import { Component } from "@angular/core";
import { Subscription } from 'rxjs';
import { MessagingService } from "./messaging.service";
@Component({ selector: "receiver", templateUrl: "receiver.component.html" })
export class ReceiverComponent {
    messages: any[] = [];
    subscription: Subscription;
    constructor(private msg: MessagingService) {
        // subscribe to home component messages
        this.subscription = this.msg.onMessage().subscribe(message => {
            if (message) {
                this.messages.push(message);
            } else {
                // clear messages when empty message received
                this.messages = [];
            }
        });
    }

}

Because of the non-linear flow of interactive UI programs, it is ideal to use Observables to handle communication between components.

Conclusion

As a reactive programming API, RxJS enables us to write elegant asynchronous UI code. For Angular UI programming, any part of the code that requires handling asynchronous behavior should leverage RxJS Observables. This helps in building powerful reactive UI experiences with minimal code. This is because:

  • RxJS Observables, along with all the RxJS operators, are more powerful than the ES6 native promises.
  • Angular's tight integration with RxJS means some Angular features are best leveraged with the use of RxJS Observables.

Punit Sethi

About the Author

Punit Sethi has been working with large e-Commerce & B2C websites on improving their Site Speed & Scalability. He frequently tweets here.