Understand Enough RxJS for your basic Angular needs!
One of the biggest changes that the Angular team introduced with the complete rewrite of Angular was the adoption of RxJS and Functional Reactive Programming as a core part of the framework.
For many, it was the holy grail. But for others, instead, it was an obstacle towards adopting and learning Angular — possibly a reason to say away from the framework.
I want to help you demystify Rx and learn enough about it to have a smooth learning experience with Angular.
RxJS is undoubtedly a big shift compared to how we used to write code in Angular.js. At first, it is scary for new people to digest and developers mostly hate things they don’t know.
On top of that, at the time of writing, RxJS provides 125 operators — that’s quite a lot! How the heck can you learn Angular and Rx if I have to remember and learn all these operators?
Well, the good news is, you don’t have to learn all the operators. In fact, the amount of operators you will use daily is no more than a dozen. Also — many operators are simple variations of a base operator: they may accept different arguments, or change slightly in behavior, but ultimately they share many similarities to one another.
At the end of this article, you won’t be writing observables like a wizard — but you will have probably gained enough knowledge to start writing and learning enough Angular code with the help of Observables.
Kind of.
You don’t need to know everything about it to perform common operations such as fetching data over HTTP requests or retrieving the query parameters of the current page, or handling very complex asynchronous code.
With that said, not knowing some crucial parts of how Observables work, and how they differ from Promises, will probably result in your code being inefficient and buggy.
RxJS is a library that helps to deal with asynchronous code. Alright, that is a simplified statement — but it’s enough for you to wrap your head around it.
The most important concepts you need to know about Rx are:
An Observable is a stream of values emitted over time. In code, it is often represented with the dollar sign $
as a prefix.
For example, by using the fromEvent
operator, we can create an observable that listen to changes to a DOM element element
on input
events.
We will also provide an Observer, which is the handler executed when the observable emits notifications; finally, we create a Subscription, which we will use to unsubscribe the observable after 2 seconds.
// values$ is the Observable
const values$ = fromEvent(element, 'input');
// observer is the Observer
const observer = {
next(value) {
console.log(value);
}
};
// subscription is the Subscription
const subscription = values$.subscribe(observer);
Notice: an observable won’t start emitting notifications until we subscribe to it! Which means you don’t just have to define your pipeline, but you also need to provide an Observer and pass it to the subscribe
function.
Also notice: unsubscribing observables is very important to avoid memory leaks and possible bugs. You can do it by calling the .unsubscribe
method on the subscription object, but we will soon see another more declarative way of doing the same.
Now, let’s rewrite this with plain DOM Javascript to see the differences:
const handler = (value) => {
console.log(value);
};
const listener = document.addEventEventListener(
element, 'input', handler
);
Even though this is an extremely simple situation, as you can see, the differences are not striking.
Now, let’s make this code slightly more complex, so we can appreciate a little bit more what Observables bring to the table. We want to:
Let’s start with the DOM example this time:
const handler = (event) => {
const value = event.target.value.toUpperCase();
if (!value.trim()) {
return;
}
console.log(value);
};
const listener = document.addEventEventListener(
element, 'input', handler
);
setTimeout(() => listener(), 2000);
And now, let’s replicate the example with RxJS:
import { map, filter, takeUntil } from 'rxjs/operators';
import { timer } from 'rxjs';
const values$ = fromEvent(element, ‘input’).pipe(
map((event) => event.target.value.toUpperCase()),
filter((value) => Boolean(value.trim())),
takeUntil(timer(2000))
);
values$.subscribe(console.log);
There’s some magic we need to explain there. First of all, we introduced 3 operators and one creational operator.
The first two operators should be familiar: you probably use them extensively with Javascript arrays:
map
take a value as a parameter and return a new valuefilter
will take the value (transformed by the previous operator)Finally, takeUntil
is an operator that accepts another observable: when the observable passed in input emits a value, then the observable will be unsubscribed reactively.
What does timer
do? This is a creational operator, as in, it creates a stream of events that will emit every 2 seconds. That is, once it emits, it will notify the operator takeUntil
to unsubscribe the stream.
The Angular team baked in Rx in the very core of the framework. As a result. we will be using observables in common operations you’ll probably be using every day.
One of the most common situations where you’ll be faced with Observables is the HTTP module.
In fact, every HTTP request is an Observable, with one difference: HTTP requests will automatically complete and do not require us to unsubscribe the observable.
class ApiService {
constructor(private http: HttpClient) {}
getUsers() {
return this.http.get('users');
}
}
// Somewhere in your component
this.api.getUsers().subscribe(users => {
// do something with users
});
In order to catch errors, which is a very common situation while working with HTTP requests, we can use the operator catchError
and handle the response gracefully.
This operator needs a new observable to be returned, which means that most times we may want to pass a value wrapped within a creational operator, of
. This operator allows wrapping a value in an observable that will synchronously emit the value. The observable will complete right away.
this.api.getUsers().pipe(
catchError((error: HttpErrorResponse) => {
return of([]);
})
).subscribe(users => {
// do something with users
});
In the snippet above, we attempt to retrieve users using an HTTP call. If this fails, we gracefully handle the error by returning an empty list.
Angular reactive forms are some of the most powerful tools that the framework provides, and their reactive nature is what makes them so great.
Every Angular FormControl
receives notifications when its value or status gets changed thanks to the observables valueChanges
and statusChanges
.
const control = new FormControl('');
control.valueChanges.subscribe((value) => {
console.log('Hey, I changed! My value is:', value);
});
control.statusChanges.subscribe((status) => {
console.log('Hey, I changed! My status is:', status);
});
You can also use the sync values attached to the FormControl instance, value
and status
, but there are instances where you should be preferring their asynchronous counterparts for a few reasons:
ChangeDetectionStrategy.OnPush
, and you want your templates to re-render automatically as the value or the status of a form change, you can leverage the observables and the async
pipe and automatically update the viewcanSubmitForm$
based on both the value and the status of the formAs we have just mentioned this, we cannot ignore the magicalasync
pipe. Rather than creating a static property within the component that will be set within the observer, you can reference and subscribe your observables directly within the templates.
// component
this.value$ = control.valueChanges$.pipe(
map((value) => value.toUpperCase()),
);
// template
<span>{{ value$ | async }}</span>
Of course, this works with any observable: HTTP requests, routing parameters, WebSocket streams, etc.
The other great thing about the aysnc
pipe is that it cleans up after itself: it will unsubscribe from the observable for you when the component is destroyed, so you don’t need to do it at the component level.
In order to pass messages between components in the same view, Angular makes use of EventEmitter
, that is an extension of RxJS Subjects
.
What is a Subject
? A Subject
is different from an Observable as its value can also be pushed in from the outside.
That means, you can subscribe to a Subject and retrieve its values over time, but you can also push values into its stream. Subjects can be particularly useful for state management.
In the example below, we can see the usage of an EventEmitter
used to dispatch outputs from a component.
@Component({
template: `
<button (click)="onCancel.emit()">Cancel</button>
`
})
class CancelButtonComponent {
@Output() onCancel = new EventEmitter();
}
Unsubscribing observables is one of the most ignored things I see from code written by beginners. Of course, most of us are simply not used to doing it as Promises don’t need to be unsubscribed.
But, as we said, Observables are values emitted over time — and thus, if we don’t stop them at some point, they will keep emitting notifications, which can lead to memory leaks or even unwanted bugs.
Not all observables, though, need unsubscribing. Some Observables will, in fact, unsubscribe themselves automatically. Let’s see some examples.
Already seen in the example above, of
is the simplest creational operator. It will take a value and wrap it in an observable. Once it’s finished, it will complete.
const stream$ = of(0, 1, 2, 3);
const subscription = stream$.subscribe((n) => {
console.log(n);
});
console.log(subscription.closed);
// Output
// 0
// 1
// 2
// 3
// true
Similarly, HTTP calls, Event Emitters, and Async Pipe do not need to be unsubscribed.
Some, probably most, observables will listen to a source of data, such as DOM events, WebSocket streams, timers and intervals, Subjects, NGRX Store selections, etc. that will keep emitting notifications until they are unsubscribed.
This is the sort of observables that can get you in trouble. A common error is selecting a slice of data from an NGRX Store without unsubscribing it:
getData() {
return this.store.pipe(select(myData));
}
Every time we call the method getData
we create a new subscription. If we don’t unsubscribe from it, we will create lots of subscriptions that will emit notifications to the observer. Not only this is a memory leak, but it can also create bugs.
In this sort of scenarios, we can use the operator take
:
getData() {
return this.store.pipe(
select(myData),
take(1)
);
}
This operator will take n
emissions before unsubscribing. This ensures that we never create multiple subscriptions.
Last but not least, let’s talk about the differences between a Promise and an Observable.
First, let’s mention the one thing they have in common: they both help with asynchronous code.
In what whey though?
RxJS helps to work with existing code written with promises by providing a set of utilities. In fact, we can:
In many instances, for instance, if you are upgrading from the previous version of Angular, you may have some code written using entirely Promises.
It can be a pain to mix two different techniques for handling async code: one of the things you could do is to transform a Promise into an Observable so that you can more easily refactor the service when it will be upgraded.
const request = this.legacyService.getUsers();
const observable$ = fromPromise(request);
observable$.subscribe(console.log);
In some other cases, it can be useful to turn Observables into Promises:
async/await
syntax to resolve multiple values and write them in a synchronous fashionasync submit() {
const users = await this.http.get('/users').toPromise();
const result = await this.http.post('/data', ).toPromise();
console.log(result);
}
Of course, in a real-world scenario, you may want to do some error handling, in which case the snippet would become a bit terser.
Learning RxJS may take some work, and probably will need you to change your perspective on a few concepts. The number of its operators may seem scary and steep — but don’t let that keep you from learning it: in reality, you don’t need to learn them all, and once you learn the most common ones, you will have enough knowledge to write most Angular tasks.
With that said, learning to master RxJS will surely pay off and make you a better Angular developer, bu writing more declarative, reactive and efficient code.
☞ Angular Tutorial - Learn Angular from Scratch
☞ Test Driven Development with Angular
☞ Learn Angular 8 from Scratch for Beginners - Crash Course
☞ Angular 5 vs Angular 6 | What's New in Angular 6 | Features & Updates