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
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
*/
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
Notification
asonComplete
event hashasValue
set to false, and therefore it's not value-based event.
Top comments (2)
Thanks. I have a couple questions:
hasValue
istrue
on 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 havehasValue
astrue
. That's a mistake on my part, will edit it out.About
catchError()
: That operator would work similarly tomaterialize
/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.