Keeping Original Value When Transforming in RxJS

From the Series “Handy RxJS Operators”

The asynchronous streams are the loveliest thing about programming in the new era. When I was developing in Java, RxJava was the only thing that made Java bearable. When I started developing on Angular, I was glad everything was natively RxJS. And then, when I started creating the Node.js backend project at my new job, I made everything RxJS. But still, sometimes the API makes your code look tedious or confusing.

In many cases, when transforming an observable using map (or flatMap ), you want to access the original value as well. Using current RxJS API, there are a few ways to do this.

Bad Practice

One way is to store the value in a local variable and then transform it:

Listing 1. Storing an observable value in a local variable

Doing so has multiple disadvantages. Generally, mutation hurts your code. When mutating your variables in an asynchronous language / paradigm / platform, you create suicidal super mutants that are beeping around waiting to explode.

A good example would be the following:

Listing 2. The flaw of using this practice: integer and string values get out of sync

If the transformation takes some time to finish (e.g. when storing something in the database and retrieving the updated document), accessing the stored original value might be too late. In listing 2, value is overwritten before the first time it is accessed.

Good Practice

A better practice is transforming the value to a tuple, containing the original value:

map(v => ([v, `${v}`]))

But then it will look tedious when transformation returns an observable:

flatMap(id => zip(of(id), repository.getById(id)))

The live example would be the following:

Listing 3. Using zip to transform and keep the original value

Better Practice

The two operators zipMap and flatZipMap provide the “zip to the transformation result” out-of-the-box.

zipMap(x => `${v}`)

Fig 1. Marble diagram for zipMap

Also, when the transformation returns an observable, it looks concise:

flatZipMap(repository.getById)

Fig 2. Marble diagram for flatZipMap

Using flatZipMap in listing 3, the code would look like the following:

Listing 4. Using flatZipMap to transform

Conclusion

To use the original value of the observable, transform the observable’s value into a tuple, and keep the original value as one of the coordinates. The operators zipMap and flatZipMap makes it easy to do so. Also, after one of the values are not needed anymore, one can get rid of it easily with the operators projectToFormer and projectToLatter :

Listing 5. Using projectToLatter

More documents on these operators and more could be found at RxJS-Pipe-Ext.