RxJS in Angular: Part III

Post Editor

In my previous two articles we have discussed how to change our components which solve problems in imperative ways to do that in functional, reactive, RxJS way, and we of course had a lot of fun doing that.

4 min read
post

RxJS in Angular: Part III

In my previous two articles we have discussed how to change our components which solve problems in imperative ways to do that in functional, reactive, RxJS way, and we of course had a lot of fun doing that.

post
post
4 min read

In my previous two articles we have discussed how to change our components which solve problems in imperative ways to do that in functional, reactive, RxJS way, and we of course had a lot of fun doing that.

In this new article we are going to go beyond that and try to solve problems without resorting to imperative approach first. Rather, we want to start thinking in a reactive way right away, visualizing our problem solving in terms of data flow and streams, rather than commands.

To do this, let's just start with a problem which is actually hard to crack using only imperative approach.

Implementing a "You have been inactive for a while" popup in RxJS
Link to this section

If you use PayPal on your desktop browser, chances are you have seen this screen at least once:

Screenshot of PayPal's "We have logged you our for your safety "
PayPal logs you out if you are inactive for a while

Now obviously this feature can be very useful (and even important regarding end user security), so we must look for ways of implementing it the most explicit and simple way possible. So instead of trying to do it in an imperative way (using setTimeout and whatnot) and then moving to RxJS, we will just implement it with RxJS from scratch to improve our reactive thinking.

First of all, we have to understand what the stream of our data will be. The final information we want to receive is a boolean value about if "has the user performed some sort of event like move the mouse or click somewhere in the past one minute or so?", so to get it we need to monitor and buffer those events for a while, using interval:

<>Copy
const perMinute$ = interval(60_000);

After that we should decide the events we want to listen to, which we will consider as the user being somehow active, and merge them:

<>Copy
const events$ = merge( fromEvent(document.body, 'click'), fromEvent(document.body, 'mousemove'), fromEvent(document.body, 'scroll'), );

This list of events is far from complete, but you can add as many events as you want to fit you needs.

After that we want to count how many events in a minute have the system recorded, and this is the most tricky part, but RxJS again provides us with a beautiful solution; an operator called bufferWhen, which receives another Observable to monitor events. What it does is start collecting events from the source stream, push them into an array, then, when the inner Observable emits, emit that array and start over. In our case we will use our events$ Observable as the source, and count the events using the interval timer (one minute):

<>Copy
const bufferedEvents$ = events$.pipe( bufferWhen(() => interval$), ).subscribe(console.log);

If we open the console now and interact with the page, we will something like this:

Huge amount of events in just several seconds

Here we see that every 5 seconds (I have reduced the interval size to 5 secs for the purpose of not having 10,000+ events in the console) our Observable emits an array of all specified events in that past timeframe. So how can we know that the user was inactive? Well, if there have been no events in that time interval, the resulting Array will be empty, thus our final code will look like this:

<>Copy
const bufferedEvents$ = events$.pipe( bufferWhen(() => interval$), filter(events => events.length === 0), // no events in a timeframe means an empty array ).subscribe(() => alert('You have been inactive for a minute!'));

RxJS can take a pretty complex problem and solve it in a declarative fashion in so few lines of code, that it can easily fit into a tweet!

Using RxJS to get dynamic information about the DOM
Link to this section

Imagine a scenario where we want to create a "scroll to top" button on our page, but we want to show it only when the user has scrolled down a bit. We could write a HostBinding, recalculate the distance fom top using window.scrollY, compare it to a sufficient value (let's say the user has scrolled for 500px), store in a property as a boolean... but then again, we promised to use RxJS right away. So, let's turn on our reactive thinking: what is the information we need? It is whether the user has scrolled sufficiently, so window.scrollY. When does this information change? When the user scrolls, of course. So, the source of our stream is the scroll event, and we should map it to the scrolled distance. Here we go:

<>Copy
@Component({ selector: 'my-app', template: ` <div> lots of content that creates vertical scroll here <button *ngIf="showBtn$ | async">Scroll to top</button> </div> `, }) export class MyComponent { showBtn$ = fromEvent(document, 'scroll').pipe( map(() => window.scrollY > 500), ); }

So, now this is very concise, short, and works perfectly!

Well, almost. If we add a tap(() => console.log('Working')) to our Observable, we would see that the even is triggered just too many times. How we fix it? Well, to be completely honest we don't need to recalculate the distance all the time; it's fairly sufficient to debounce that process a little. See where I'm going with this? Yes, we can just add debounceTime(50) to make it run after there has been a 50 milliseconds (yes, 50 seconds ) pause between scrolling. This will significantly reduce the amount of value changes (and re-renders in some cases) that will happen without taking away the smoothness of transition of button being visible and not being visible. While this is a minor optimization, it opens door to a huge discussion about how we can make our code in Angular more performant using RxJS, which we will dive into deeper in the next chapter.

Share

About the author

author_image

Senior Angular developer from Armenia. Passionate about WebDev, Football, Chess and Music

author_image

About the author

Armen Vardanyan

Senior Angular developer from Armenia. Passionate about WebDev, Football, Chess and Music

About the author

author_image

Senior Angular developer from Armenia. Passionate about WebDev, Football, Chess and Music

Reviewed by
Max Koretskyi
NxAngularCli
NxAngularCli
NxAngularCli

Featured articles