The web content describes an RxJS challenge to create a "non-flicker loader" that only displays a loading indicator if data takes longer than one second to load and ensures it remains visible for at least one second to prevent a flickering effect.
Abstract
The article presents a coding challenge for developers using RxJS, aiming to refine the user experience during data loading. The challenge, named "RxJS Challenge #17: Non-flicker loader," involves creating a loading pattern that avoids showing a loading spinner for brief periods when data loads quickly, typically under one second. If data loading exceeds one second, the loader appears and remains visible for at least one second, even if the data arrives sooner, to prevent visual flickering. The article provides a boilerplate on StackBlitz for developers to experiment with the solution, which involves setting up a macro stream with triggering subjects for loading and cancellation, and defining a nonFlickerLoader function with generic typing for the response data. The solution uses various RxJS operators, such as combineLatest, takeWhile, map, repeat, and distinctUntilChanged, to manage the loading state and data display effectively. The article also includes a discussion of the behavior for different loading durations and provides a live example on StackBlitz. It concludes with a mention of the authors' other Angular open-source projects and an invitation for readers to support their work by purchasing merchandise or following them on Twitter.
Opinions
The authors believe that preventing the flickering of loading indicators is crucial for a smooth user experience.
They suggest that showing a loader for at least one second after it appears provides a more stable and user-friendly interface.
The authors are proponents of using RxJS for handling complex asynchronous operations, such as data loading with conditional indicators.
They advocate for the use of error handling in RxJS streams, referencing a previous challenge that covered this topic.
The authors are involved in the Angular community, maintaining open-source projects and encouraging engagement through social media and merchandise support.
RxJS Challenge #17: Non-flicker loader
When you load data you never know how long it would take. It’s annoying for the user to see spinner for just a fraction of a second when data loads very fast. Let’s try to create a pattern that we would call “non-flicker loader”. Here are the requirements:
If loading took less than 1 second — we will not show loader at all
If loading took more than a second — we start showing loader and keep it on the screen for at least 1 second
For example our data took 1.2 seconds to load. For the second we do not show anything then a loader appears. After .2s we receive data but we keep loader on screen for .8s more so it does not appear flickery.
This is an interesting task. It might not be obvious how to approach it. Let’s first set up macro stream isolating all the logic into a separate function:
Here we have two triggering subjects to start and cancel loading. We assume our nonFlickerLoader emits null when loading should be indicated and actual data when we should display it. We use repeat so we can trigger it multiple times.
Now let’s go ahead and write that nonFlickerLoader function. First we define the interface. We use generic <T> because we do not know the type of response we will be getting:
This function takes the data Observable and two optional arguments for how long we wait before we show the loading indication and minimal duration for how long this indication should be shown.
In that function body we will create the following Observable:
It tells us if loading indicator should be visible. map turns every emit except the first one to false. takeWhile is used to only let through the first emit, after delay time expired and the second after duration has passed. Note that we used second argument to let the false value that failed Boolean condition through before completing the stream. And we also startWith(false) because we only want to show loading indication after given delay.
Now let’s see how we can intertwine this Observable into data request:
We use combineLatest. This operator only emits values after all child Observables have emitted something. loading$ starts with false and we add startWith(null) to our data request to kickstart the stream.
We use clever takeWhile function here. It would only let through emits with falsydata (initial null ) or truthyloading which is when we need to show loading indicator. And when condition is not met we also let the final value through thanks to the second argument.
Then we map the result. It’s either null if loading is true or it’s data. Remember that data is null until we receive actual value.
We use skip(1) because we do not want our startWith(null) for data stream to come through. And we use distinctUntilChanged so multiple null values will not pass through as well.
🤔 So what happens now?
1 Loading took less than 1 second. Our initial null is skipped by skip(1) and data$ emitted truthy value before loader emitted true. This means takeWhile condition is failed and we terminate the stream letting data through (data is not falsy and loading is false).
2 Loading took 1.5 seconds. Now we have data$ emitting null and loading is true. This meets takeWhile condition and gets mapped to null. We use this null to show loading in our macro stream. Next data$ emits the value, but loading is still true. So takeWhile lets it though and the value is once again mapped to null which gets filtered by distinctUntilChanged. After full second is passed loading emits false and takeWhile terminates the stream. This last emit gets mapped to the value that data$ emitted last time and we hide loading indicator and show the data.
3 Loading took over 2 seconds. The beginning is the same, but instead of data$ emitting value we now have loading emitting false as showing loading indicator is no longer required. However data is still null so takeWhile keeps the stream alive and map turn it to null. But as soon as we have value from data$ — stream is completed and map returns the actual data we want to show.
You can see this in action here:
One thing this solution is missing is error handling but we covered it already in one of the previous challenges. And once you’re done with everything here, you are welcome to move to the next one!
🌲 What do we do?
We create and maintain many Angular open source projects, namely: