Maintain shared observable state using the scan and shareReplay operators

Rares Matei
InstructorRares Matei
Share this video with your friends

Social Share Links

Send Tweet
Published 5 years ago
Updated 4 years ago

The scan() is very useful in RxJS. It allows you to maintain a running state over time. In this lesson, we'll look at some of the state types scan can hold: transient or single source of truth, and how we can achieve each of them by combining it with the share operators.

We'll also look at the differences between share() / shareReplay(1) / shareReplay({bufferSize: 1, refCount: true}) and how to avoid memory leaks when using them.

Instructor: [0:00] We'll take a quick break from building our app to look at this. An emission's observable that emits a 1 any time we click on this button. If we look at the console here, when we click on emit, we get an emission and then we pipe these emissions to a scan that adds up the numbers in the same way we've been doing in our app.

[0:18] If we click multiple times, we get increasing values and we subscribe to this here hence why we get to the console logs. Have a look at this. If I click this button here, it will add a new subscriber to our source, and this is the callback where we actually subscribe.

[0:33] If we click on emit now, we can see that our first subscriber got unexpected state, the number 4, but our second subscriber got a number 1, which is the initial state. Scan keeps a brand-new state for each subscriber.

[0:48] Another way you can think about this is, what type of state is scan holding? Is it transient? Do we want to reset it per subscription? We'll see an example of this later on in our app. Or is it a single source of truth that is shareable across all subscribers? A Redux store would be a good example of this. It's shared and you don't want it to reset per subscription.

[1:12] Let's add the share operator after our scan. We emit only to the first subscriber initially. Then once we bring in the second subscriber and then we emit, we get the same state for both of them now.

[1:24] That's great, but after we added the second subscriber, there was this period of time where we didn't know what the latest value is. The second subscriber only got that value once the source emitted again. Let's switch this to a shareReplay of 1 and then emit a bunch of times and then add a new subscriber.

[1:45] We can see that it gets a new value straight away and for any future values, they'll be in-sync again. This one is our buffer size. It means that it will hold the latest 1 value and send it immediately to any new subscribers. Our scan state is now a single source of truth. It's shared and immediately knowable by all new subscribers.

[2:07] But now, if we click this button, tear down everything, it's going to unsubscribe from both of the subscriptions that we created. We can see that down here in the code as well. If we click on emit, we click multiple times, we just keep clicking, and we don't get any messages now, which makes sense.

[2:25] Let's try and add the second subscriber back in. What state do we get? We get 29. Where is this coming from? It turns out that if you use the default mode of shareReplay, it will keep a subscription to its source alive even after everything has unsubscribed from it. All that time we were clicking on emit, thinking it had no effect, it was racking up values in the background silently.

[2:51] That's why we got all the way up to 29. This is also potentially dangerous for memory leaks as it will never unsubscribe from the source. Instead of for 1, we'll pass in this object where we explicitly set the buffer size to 1 and the refCount to 2.

[3:08] RefCount will keep track of our references, our subscribers, and when the number of subscribers drops to , it will also dispose of its source. If we try that again, we emit a bunch of times, we add a new subscriber, then we unsubscribe from everything. Now we're going to emit a bunch of times in the background.

[3:28] Once we add the second subscriber, we don't get any value because there's no initial value. We can see that if we click emit, we're going to get the value 1 again. Most of the time this is the safe way to use shareReplay, and you want to use this option.

[3:42] Let's go back to our app and think about our scan. This will definitely fall in the second category as our current load count is a single source of truth. There's only one true count of background tasks at any one point in time Let's add shareReplay at the end of it. I'll just import it up here.