DEV Community

Wojciech Matuszewski
Wojciech Matuszewski

Posted on

RxJS Notification and materialize / dematerialize operators

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.

Top comments (2)

Collapse
 
boneskull profile image
Christopher Hiller

Thanks. I have a couple questions:

  1. hasValue is true on complete? Completion doesn’t emit a value afaik
  2. Can you not use catchError() to trap errors and continue?

While a relative novice with RxJS myself, I’m still unclear about the use case for notifications short of building debugging tools.

Collapse
 
wojciechmatuszewski profile image
Wojciech Matuszewski

Hi Chris, thanks for reading.

About onComplete: You are completely right! It does not have hasValue as true. That's a mistake on my part, will edit it out.

About catchError(): That operator would work similarly to materialize / dematerialize combo. These operators are pretty 'niche' and I just wanted to write about them so that people know they exist (just like I did not know them).

It's hard for me to come up with some reasonable, day-to-day, use case (except debugging tools, just like you mentioned), but maybe, one day, they will be all someone needs to solve an issue they are currently facing.