The better side of the Single value Observables in RxJs

Picture of waterfall.
Photo by v2osk on Unsplash

The RxJs library is a great set of tools that can help you bring your application reactivity to the next level. One of the various features of Observables is that they return a series of values. You can simply connect to a stream of data and receive various values over a period of time. A great example of this is listening to the mouse events or a stream of data that comes from a Websocket. On the other hand, sometimes there are situations when you only want to get one value from a stream and complete it. In these cases, multi-value observable seems like a bit over-engineering. The perfect real-life example can be fetching data with an HTTP call. It's common to use the RxJs based library for HTTP requests like HttpClient from the Angular or axios-observable, because usually data from the request is returned after a period of time. Let's take a look at example code:

class HttpClient {
    get<T>(url: string): Observable<T> {...}
}

We know that observable will return only one value and it will complete as this is the nature of HTTP requests. So why is the return type an Observable which by definition suggests a series of values. It's counter-intuitive and can put a reader into confusion and raise questions like "Does this really return one value ?"🤔.

In this article, I will present my thoughts on how to make RxJs code more readable.

Convention

In one of the last projects, that I've had the pleasure to work on we had a convention of naming all the functions and methods that return an observable with the prefix on. The programmer instantly knew what type of observable the function has returned just by taking a glimpse at its name. In some cases when we wanted to get a single value observable we named the method once. It worked for us really well and I highly recommend it to other teams which face similar issues.

class UserStore {
    // return stream of Users which may change in the future
    onUsers(): Observable<Array<User>> {...}
    // returns an Array of Users once and completes
    onceUsers(): Observable<Array<User>> {...}
}

Unfortunately, it wasn't possible to force type safety in the code with this convention. Functions still returned observable and it was not possible to predict if this observable will return a single value or many of them.

Code conventions are great
Code conventions are great

Single

The solution to this challenge may be the concept of Single. The official documentation of ReactiveX describes Single in the best possible way:

A Single is something like an Observable, but instead of emitting a series of values - anywhere from none at all to an infinite number - it always either emits one value or an error notification.

Let's take a look at the previous example but this time let's use Single as a return type.

class HttpClient {
    get<T>(url: string): Single<T> {...}
}

A small change to the code notably increases its readability.

It's not enough for code to work.

- Robert C. Martin

The JavaScript implementation of Rx doesn't provide us with a type of observable that matches the description above. There are a couple of the RxJs operators which can do what we need e.g. take, first and single. The last one even has the same name as the proposed Single type. This single operator returns an observable that emits only one value, we can take a look at the example code below:

const source$ = of(1, 2, 3);
//emit one item that matches predicate
source$
    .pipe(
        single()
    )
    .subscribe(console.log);
//output: 1

As we can see the single operator transforms observable into one which returns the first value. You may say that our work is done as this solves our issue, right? Well, what about this:

function requestHttpCall(): Observable<Response> {
return httpCall$     // basic http call
           .pipe(
               single()
           );
}

Unfortunately the operator single doesn't change the observable type. We still receive an actual observable with all of its various features. This solution doesn't provide the readability which would be given by a dedicated return type. It lacks semantic cohesion.

The observable type is not changed by operators
The observable type is not changed by operators

The RxJs team recognizes the issue and considers the idea of introducing Single class into the library (you may read about it in this GitHub issue). Nevertheless, as of today no changes have been introduced.

Now that we have seen the proposed solutions provided by the RxJs library let's get to coding and try to implement our own Single class.

Implementation

To summarize the requirements we know that Single is a special type of Observable which only allows calling one of the methods once: next, error, complete. After calling either of these, the Single should complete and finalize the subscription. Let's have a look at the most simplistic implementation:

class Single<T> extends Observable<T> {

   static from<T>(source$: Observable<T>): Single<T> {
      const single = new Single<T>();
      single.source = source$.pipe(take(1));
      return single;
   }

   protected constructor(subscribe?) {
      super(subscribe);
   }

}

The implementation is really not that complicated. We create Single with a static creator method from which basically assigns take(1) operator to the source observable. The only thing is that you need to remember to create an Single instance by calling the Single .from method.

Now we can make adjustments to the UserService:

class UserService {
   // ... rest of
    onUsers(): Observable<Array<Users>> {
        return this.repository.selectUsers();
    }
    onceUsers(): Single<Array<Users>> {
        return Single.from(this.onUsers());
    }
}

The onceUsers method returns our new Single type which informs a programmer that observable will contain only one value. The UserService class is written with the "on & once" convention in addition to Single type, which makes it easier to understand.

One may argue that method onceUsers could be written in a way:

class UserService {
    // ..
    onceUsers(): Observable<Array<Users>> {
        return this.onUsers().pipe(take(1));
    }
}

Unfortunately by implementing it this way you lose the information that observable provides only one value. When you decide to use UserService in a different layer of the application, the return types stay Observable. With the previous implementation, the Single type stays with the stream and the TypeScript compiler will provide additional info to the programmer.

Conclusions

Streams of data are really helpful but you don't always want to subscribe to many values. Sometimes you just need to check the actual value and that's it. In this situation, the Single type may really increase the readability of your code and help you catch unwanted bugs. Combining it with the "on & once" convention brings your reactive code to a higher standard of clean code.

Luke, Software engineer, passionate about best practices, frameworks React, Vue, patterns and architectures for modern web
				development, open-source creator, TypeScript enthusiast, Generic UI Technical Blog

Luke

Software engineer, passionate about best practices, frameworks React & Vue, patterns and architectures for modern web development, open-source creator, TypeScript enthusiast.