RxJS Notification and materialize / dematerialize operators

Wojciech Matuszewski - Jul 28 '19 - - Dev Community

This article assumes basic knowledge of RxJS.
If you are unfamiliar with RxJS, I highly recommend reading through the docs.

Recently, while scrolling through the RxJS docs looking for inspiration on how to solve one of the problems I was facing, I noticed a type (and subsequent operators that go along with it) that I never used: Notification.

Today, I want to share what I learned fiddling with it.

Notification

Docs state that Notification is:

[...] a push-based event or value that an Observable can emit. ... Besides wrapping the actual delivered value, it also annotates it with metadata

Let's try to break it down, piece by piece.

Event or value that an Observable can emit

Observable can emit*

  • onComplete
  • onError
  • onNext

Let's try to replicate this behavior using Notification API.

import { Notification, from } from "rxjs";

// Notification(Type, Value?, Error?)

const onErrorNotification = new Notification("E",{}, 'onError' );
const onCompleteNotification = new Notification("C", 'onComplete');
const onNextNotification = new Notification("N", 'onNext');

from([onNextNotification, onCompleteNotification, onErrorNotification])
  .subscribe(console.log);

/* {
 *  kind: 'E' or 'C' or 'N'
 *  value: 'onNext' or 'onComplete' or undefined
 *  error: undefined or 'onError'
 *  hasValue: true or false (onNext hasValue is always true)
 * }
 */

Enter fullscreen mode Exit fullscreen mode

I did not use onComplete and onError handlers to log the values, why?

It's because Notifications are treated as onNext events, but they (in this case) represent the underlying events and values, so onComplete and onError handlers would never trigger.

Annotates it (the actual value) with metadata

There is a great deal of information that Notification carries:

  • It tells you the type of event (with kind prop)
  • It exposes to you the actual value (with value prop)
  • It informs you about the error (with error prop), and it's value
  • It tells you if the event is value-based (onNext)

It's all great and all, but how do we actually transform the Observable event to a Notification?

Enter: materialize and dematerialize

materialize and dematerialize

These operators are pretty interesting.
They allow you to control in which 'realm' of events or values (Notification or normal, Observable based) you currently reside.

dematerialize

This operator allows you to 'degrade' Notification to the underlying Observable event or value that given Notification was.

Let's remake the first code example so that we actually have to have all 3 handlers attached (onNext, onError, onComplete) to get all data.

from([ onNextNotification, onErrorNotification, onCompleteNotification ])
  // 'degrading' to an Observable-event world
  .pipe(dematerialize())
  .subscribe({
    next: console.log,
    error: console.log,
    complete: console.log
  });

/* onNext
 * onError
 */
Enter fullscreen mode Exit fullscreen mode

Why was not onComplete logged out?

Any given stream can only error out once. This is defined by the Observable contract, which says that a stream can emit zero or more values.

In our case, it means that the stream has ended its life cycle with an error and will not emit any further values.

This situation hints on the use case where we want to, despite the error, carry on with our operator chain.

materialize

Just like, dematerialize 'degraded' events, materialize, enables you to 'promote' given event to a Notification type.

Let's say we know our source Observable can randomly throw, but we still want to go through our operator chain.

import { dematerialize, materialize, map, delay } from "rxjs/operators";
import { Notification, timer } from "rxjs";

sourceThatThrows$
  .pipe(
    // Promoting the event to a Notification
    materialize(),
    map(notification =>
      // Was the event value based? (onNext, onComplete)
      notification.hasValue
        ? notification
        // As alternative you could also use new Notification(...)
        : Notification.createNext("was error value!")
    ),
    delay(100),
    dematerialize()
  )
  .subscribe(console.log);

/* value from source
 * was error value
 */
Enter fullscreen mode Exit fullscreen mode

Using, materialize and dematerialize operators we successfully preserved our operator chain even though the source can randomly throw.

Summary

RxJS ecosystem is vast, with 100+ operators there is definitely much to learn.
I hope, I was able to shed some basic knowledge on these particular two.

You can follow me on twitter @wm_matuszewski

Thanks 👋

Footnotes

* I'm not an expert, there is probably much more stuff that Observable can emit. For the sake of this article, I assumed those three events.

Edit:

  • Thanks to Christopher Hiller for pointing out that Notification as onComplete event has hasValue set to false, and therefore it's not value-based event.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .