This content originally appeared on Level Up Coding - Medium and was authored by Nic Chong
Introduction
Observables enable us to asynchronously stream data in our applications, as you are probably already aware of. You might not be aware, though, that we can also “multicast” observables, which means that we can share the values of those variables with numerous subscribers.
In this article, we’ll look into multicasting and how Angular apps can make use of it. Before diving into several instances to show multicasting in action, we’ll first define it precisely.
What exactly is multicasting? It is fundamentally a method of distributing a single execution of an observable among numerous subscribers. To put it another way, all subscribers share a single execution of the observable and receive the same data rather than each one triggering a separate execution. This can be very helpful when you wish to avoid sending the same data in several HTTP requests, for example.
Here is a straightforward example to show how a standard observable and a multicasted observable differ from one another. Consider an observable that sends out the time every second:
const time$ = interval(1000).pipe(map(() => new Date()));
If we have two subscribers listening to this observable, like so:
time$.subscribe(time => console.log('Subscriber 1:', time));
time$.subscribe(time => console.log('Subscriber 2:', time));
Each subscriber will trigger a separate execution of the observable, resulting in the following output:
Subscriber 1: Tue Jan 05 2021 10:32:00 GMT+0800
Subscriber 2: Tue Jan 05 2021 10:32:01 GMT+0800
On the other hand, if we multicast this observable using the share operator, the subscribers will share the same execution and receive the same values:
const time$ = interval(1000).pipe(
map(() => new Date()),
share()
);
time$.subscribe(time => console.log('Subscriber 1:', time));
time$.subscribe(time => console.log('Subscriber 2:', time));
This results in the following output:
Subscriber 1: Tue Jan 05 2021 10:32:00 GMT+0800
Subscriber 2: Tue Jan 05 2021 10:32:00 GMT+0800
As you can see, multicasting has the potential to be an effective technique for improving the functionality of our Angular apps. We’ll go into more detail about the many methods we can multicast observables in Angular in the next sections.
Multicasting with the share operator
We introduced the concept of multicasting observables and explained how it allows multiple subscribers to share the same execution of an observable. In this section, we’ll take a closer look at how to achieve multicasting in Angular using the share operator.
First, let’s start by creating a non-multicasted observable that emits a value every second:
const source$ = interval(1000).pipe(map(x => x + 1));
If we have two subscribers listening to this observable, like so:
source$.subscribe(x => console.log('Subscriber 1:', x));
source$.subscribe(x => console.log('Subscriber 2:', x));
Each subscriber will trigger a separate execution of the observable, resulting in the following output:
Subscriber 1: 1
Subscriber 2: 1
Subscriber 1: 2
Subscriber 2: 2
To multicast this observable using the share operator, we can simply add it to the pipeline:
const multicasted$ = source$.pipe(share());
Now, if we have two subscribers listening to the multicasted observable, they will share the same execution and receive the same values:
multicasted$.subscribe(x => console.log('Subscriber 1:', x));
multicasted$.subscribe(x => console.log('Subscriber 2:', x));
This results in the following output:
Subscriber 1: 1
Subscriber 2: 1
Subscriber 1: 2
Subscriber 2: 2
As you can see, the share operator allows us to easily multicast an observable in just a single line of code.
It’s worth noting that the share operator is actually a shorthand way of using a Subject under the hood. This means that it has some of the same limitations as subjects, such as not being able to specify the number of values to store for replay. However, the share operator is often a good choice for simple cases where you just want to multicast an observable and don't need the extra flexibility of a subject.
Multicasting with subjects
Let’s first discuss what a subject is in detail before moving on to discussing how to use subjects for multicasting. A subject is a unique form of observable in RxJS (the reactive programming library utilised by Angular) that has the ability to both emit data and subscribe to observables. As a result, it serves as both an observer and an observable.
In RxJS, there are various categories of subjects, each with special qualities of its own. We’ll concentrate on the BehaviorSubject and the ReplaySubject for the sake of multicasting.
A BehaviorSubject is a subject that emits the most recent value to it to any new subscribers and stores the most recent value emitted to it. When you want new subscribers to obtain the subject’s current value right away after subscribing, this can be handy.
Here’s an example of how to use a BehaviorSubject to multicast an observable:
const subject = new BehaviorSubject(0);
const multicasted$ = subject.asObservable();
multicasted$.subscribe(value => console.log('Subscriber 1:', value));
subject.next(1);
subject.next(2);
multicasted$.subscribe(value => console.log('Subscriber 2:', value));
subject.next(3);
This results in the following output:
Subscriber 1: 0
Subscriber 1: 1
Subscriber 1: 2
Subscriber 2: 2
Subscriber 1: 3
Subscriber 2: 3
As you can see, despite the fact that they subscribed at separate times, both subscribers receive the same values from the subject.
A ReplaySubject is similar to a BehaviorSubject, but it can also store multiple values from the past and replay them to new subscribers. You can specify the number of values to store using the bufferSize parameter.
Here’s an example of how to use a ReplaySubject to multicast an observable:
const subject = new ReplaySubject(2); // Stores the last 2 values
const multicasted$ = subject.asObservable();
multicasted$.subscribe(value => console.log('Subscriber 1:', value));
subject.next(1);
subject.next(2);
subject.next(3);
multicasted$.subscribe(value => console.log('Subscriber 2:', value));
This results in the following output:
Subscriber 1: 1
Subscriber 1: 2
Subscriber 1: 3
Subscriber 2: 2
Subscriber 2: 3
As you can see, Subscriber 2 receives the last two values emitted by the subject, thanks to the ReplaySubject storing them for replay.
After seeing how to utilise subjects for multicasting observables, you might be curious how they stack up against the share operator. The share operator is really just a shortcut for using a Subject. But the share operator gives you less control over the multicasting. However, the share operator doesn't give you as much control over the multicasting behavior as using a subject directly. For example, with the share operator, you can't specify the number of values to store for replay like you can with a ReplaySubject.
That being said, the share operator is often a good choice for simple cases where you just want to multicast an observable and don't need the extra flexibility of a subject. It can also be easier to read and understand, since it's a single operator rather than an entire subject class.
In general, it’s good to have both options in your toolkit and choose the one that best fits the needs of your specific use case.
Multicasting with the publish operator
The publish operator is similar to the share operator in that it turns a regular observable into a multicasted observable. However, unlike the share operator, which immediately starts executing the observable and emitting values to subscribers, the publish operator returns a so-called "connectable observable". This means that the observable won't start executing until we specifically tell it to, using the connect method.
Here’s an example of how to use the publish operator and a connectable observable:
const source$ = interval(1000).pipe(map(x => x + 1));
const published$ = source$.pipe(publish());
At this point, the observable represented by published$ is "cold", meaning that it won't start executing until we subscribe to it. Let's say we have two subscribers:
published$.subscribe(x => console.log('Subscriber 1:', x));
published$.subscribe(x => console.log('Subscriber 2:', x));
Nothing will happen yet, since we haven’t called the connect method to start the execution of the observable. Let's do that now:
const subscription = published$.connect();
Now the observable will start executing and emitting values to the subscribers:
Subscriber 1: 1
Subscriber 2: 1
Subscriber 1: 2
Subscriber 2: 2
As you can see, the publish operator allows us to control the execution of the observable and decide when to start emitting values to subscribers.
One advantage of using a connectable observable is that we can use the refCount operator to automatically connect to and disconnect from the observable based on the number of subscribers. This can be useful for cases where you want to start executing the observable when the first subscriber subscribes, and stop executing it when the last subscriber unsubscribes.
Here’s an example of how to use the refCount operator with a connectable observable:
const source$ = interval(1000).pipe(map(x => x + 1));
const published$ = source$.pipe(publish(), refCount());
Now, if we have two subscribers:
const sub1 = published$.subscribe(x => console.log('Subscriber 1:', x));
const sub2 = published$.subscribe(x => console.log('Subscriber 2:', x));
The observable will start executing and emitting values:
Subscriber 1: 1
Subscriber 2: 1
Subscriber 1: 2
Subscriber 2: 2
If we unsubscribe the first subscriber:
sub1.unsubscribe();
The observable will continue executing and emitting values:
Subscriber 2: 3
Subscriber 2: 4
But if we unsubscribe the last subscriber as well:
sub2.unsubscribe();
The observable will stop executing and emitting values:
(nothing is logged)
As you can see, the refCount operator allows us to automatically connect to and disconnect from a connectable observable based on the number of subscribers. This can be a convenient way to manage the execution of an observable and avoid unnecessary work.
Here’s an example of a complete multicasted observable using the publish operator and the refCount operator:
const source$ = interval(1000).pipe(map(x => x + 1));
const published$ = source$.pipe(
publish(),
refCount()
);
const sub1 = published$.subscribe(x => console.log('Subscriber 1:', x));
const sub2 = published$.subscribe(x => console.log('Subscriber 2:', x));
setTimeout(() => {
sub1.unsubscribe();
}, 5000);
setTimeout(() => {
sub2.unsubscribe();
}, 10000);
This results in the following output:
Subscriber 1: 1
Subscriber 2: 1
Subscriber 1: 2
Subscriber 2: 2
(... continues for 5 seconds)
Subscriber 2: 6
Subscriber 2: 7
(... continues for 5 more seconds)
As you can see, the observable executes and emits values for the duration of both subscriptions, and then stops when the last subscriber unsubscribes.
Don’t Miss my upcoming content and tech guides:
Get an email whenever Nic Chong publishes.
If you have any questions, I am here to help, waiting for you in the comments section :)
Level Up Coding
Thanks for being a part of our community! Before you go:
- 👏 Clap for the story and follow the author 👉
- 📰 View more content in the Level Up Coding publication
- 🔔 Follow us: Twitter | LinkedIn | Newsletter
🚀👉 Join the Level Up talent collective and find an amazing job
3 Things You Didn’t know About Observables was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding - Medium and was authored by Nic Chong
Nic Chong | Sciencx (2023-01-10T18:34:34+00:00) 3 Things You Didn’t know About Observables. Retrieved from https://www.scien.cx/2023/01/10/3-things-you-didnt-know-about-observables/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.