Deborah Kurata
Posted on November 16, 2020
We've discussed Observable and Subscription. Another key RxJS concept is Observer.
What Is an Observer?
An Observer watches for emissions and notifications from an Observable after a consumer subscribes to that Observable.
An Observer defines an interface with callback functions for each type of Observable notification: next, error, and complete.
Use the next callback to process the emitted item.
Use the error callback to implement exception handling.
Use the complete callback to perform cleanup when the Observable is complete. (This is not used often in an Angular application.)
How Do You Define an Observer?
There are several ways to define an Observer.
Explicitly (Uncommon)
Though not common, you can define an Observer explicitly by creating an object with three callback functions: next, error, and complete.
// Define an explicit observer (uncommon)
const observer = {
next: apple => console.log(`Apple was emitted ${apple}`),
error: err => console.log(`Error occurred: ${err}`),
complete: () => console.log(`No more apples, go home`)
};
The next method argument is the emitted item. The arrow function specifies what to do with that item. In this case, we are simply logging it to the console.
The error method argument is the error emitted when an error occurs. The arrow function specifies what to do with the error notification. In this case, we are logging the error to the console.
The complete method has no argument. The arrow function defines what to do when the Observable is complete. In this case, it logs a message to the console.
We then pass that Observer object into the Observable subscribe method to react to the Observable's emissions and notifications.
// Pass the Observer into the subscribe (uncommon)
const sub = source$.subscribe(observer);
Pass a Single Callback
It is more common to pass the Observer callback functions directly into the Observable subscribe method.
You can only pass one object to the subscribe method.
If you only need the next callback, pass it directly as the subscribe argument.
// Pass the next callback function directly
const sub = source$.subscribe(
apple => console.log(`Apple was emitted ${apple}`)
);
Pass an Observer Object
Since you can only pass one object to subscribe, if you need to handle multiple types of notifications, pass an Observer object with the desired set of callbacks.
// Pass an Observer object with callback arrow functions
const sub = source$.subscribe({
next: apple => console.log(`Apple was emitted ${apple}`),
error: err => console.log(`Error occurred: ${err}`),
complete: () => console.log(`No more apples, go home`)
});
Notice that the above code passes an object into the subscribe method with next, error, and complete methods. You only need to specify the methods for the notifications you'll handle. So if you don't need to process the complete notification, you don't need to specify it.
What if You Don't Want to Use Arrow Functions?
The prior examples all used arrow functions, denoted with =>
. Some developers may prefer to use declared named functions instead of arrow functions when defining Observer callbacks. Like this:
const sub = source$.subscribe({
next(apple) { console.log(`Apple was emitted ${apple}`) },
error(err) { console.log(`Error occurred: ${err}`)},
complete() { console.log(`No more apples, go home`)}
});
Notice the syntax difference. Here we define each function (next) with it's parameter (apple) and function body denoted with {}.
But watch out for this
. In TypeScript (and in JavaScript), this
is scoped to the function. So if you have code like the following:
// Watch out for `this`
const sub = source$.subscribe({
next(apple) { this.apple = apple }, // Does NOT reference the
// class-level variable
error(err) { console.log(`Error occurred: ${err}`)},
complete() { console.log(`No more apples, go home`)}
});
It may not work as expected. The this.apple
will not reference a class-level variable and will instead define a function-scoped variable.
How Do the Pieces Fit Together?
The Observable, Observer, and Subscription work together to:
- Tell the Observable to start emissions/notifications
- Provide callback functions to react to those emissions/notifications
- Set up a subscription that allows for unsubscribing
Here are the concepts shown on a more formal marble diagram.
Thanks to @michael_hladky for this marble diagram.
Here is a more common example usage in an Angular application.
Service
products$ = this.http.get<Product[]>(this.productsUrl)
.pipe(
tap(data => console.log(JSON.stringify(data))),
catchError(this.handleError)
);
In the above code, products$ represents the Observable.
It is a common practice to add a
$
suffix to a variable that represents an Observable. This makes it easier to see when the code is working with an Observable.
Component
ngOnInit(): void {
this.sub = this.productService.products$.subscribe({
next: products => this.products = products,
error: err => this.errorMessage = err
});
}
In the component, the Observer object is passed into the subscribe method, defining two callbacks: next and error.
The this.sub
represents the Subscription returned from the subscribe method. This is used to unsubscribe on ngOnDestroy.
I hope that clarified the meaning of the term Observer and demonstrates how three key RxJS concepts: Observable, Subscription, and Observer work together.
Posted on November 16, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.