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)
* }
*/
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
kindprop) - It exposes to you the actual value (with
valueprop) - It informs you about the error (with
errorprop), 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
*/
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
*/
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
NotificationasonCompleteevent hashasValueset to false, and therefore it's not value-based event.
Top comments (2)
Thanks. I have a couple questions:
hasValueistrueon complete? Completion doesn’t emit a value afaikcatchError()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.
Hi Chris, thanks for reading.
About
onComplete: You are completely right! It does not havehasValueastrue. That's a mistake on my part, will edit it out.About
catchError(): That operator would work similarly tomaterialize/dematerializecombo. 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.