It’s so easy with Angular — part 3: Page loader
Ah, page loaders. An underrated piece of UI/UX. Truly. Almost everybody just uses some generic solution, but when I see a good page loader, I know I’m in for a treat. Even if content/functionality is rubbish.
Basically, nicely designed loader says: “Don’t worry, our app is working hard for you”. Badly designed loader says: “Our service is bad, but at least we haven’t crashed… yet…”.
If you’re a designer, give them more love. It’s one of those things that can hook new users. Me? I’m a grumpy backend dev who thinks God observes the Matrix through the command line, just like in the movie. He even wants to answer our prayers, but the man pages are bad, so he can’t get the command syntax right.
In the spirit of previous article, we’ll implement our own page loader from scratch. And as a grumpy backend dev, focus on the style, in spite of what I’ve written above, will be almost nonexistent. If you want a fancy moving object, search on how to do it. Or you can find some library… Or use fa spin class from font awesome. Bottom line, animation and style are CSS things, and my focus is on Angular here.
So what do we need? A component and a service of course. That is the magic recipe for everything in Angular. Ain’t that nice.
ng g c page-loader
ng g s page-loader
Once the CLI has done it’s magic, it’s time for us to do ours. First, make sure that <app-page-loader>
sits at the top of your app.component.html
like so:
We have a red squiggly line for pageLoadTest()
but we will implement that function later. What are requirements for the page loader? Well, it’s either visible or not. We’ll call that loading. We may want to include some message while doing it, so we’ll call that message. There can be an optional progress bar as well, so users can see that our app isn’t frozen. We’ll call that progress. Service file therefore starts like this:
Moving to the first obvious thing, we have to set if page loader visibility. Remember, it’s the loading subject. So to show/hide the loader we need:
Message, and progress are optional, but we must set the loading subject to true/false
. Page loader component will determine if it is visible or not based on that. Next, we want the ability to subscribe to the loading subject, and set or change message and progress value:
That’s it. That is the service in its full glory. Pretty simple. Just 3 ‘getters and setters’. Moving on to page loader component.
Not much. Be aware that I’ve left out shiny moving objects as mentioned at the beginning. But you can easily add a div with it. Also, take a mental note of that [style.width] attached to progress-bar’s div.
Some starter pack, copy-pastable CSS:
.loader-container {
position: absolute;
top: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: #000000aa;
pointer-events: none;
}.loader-message-container {
color: whitesmoke;
font-size: 1.5rem;
}.loader-progress-container {
padding: 5px;
min-height: 1.3rem;
width: 100%;
background-color: black;
}.progress-bar {
min-height: 1rem;
background-color: orange;
}
This won’t make it pretty, it will make it bearable. Style it however you want. If you’ve never seen pointer-events: none, check documentation on it. Really useful piece of CSS.
page-loader.component.ts
is just a large subscription declaration, so I’ll put it as a copy-pastable code as well:
constructor(private pageLoaderService: PageLoaderService) { }loading: boolean;
message: string;
progressValue: number;
progressValueStyle: string;
private _subscribed: boolean = true;ngOnInit(): void {
this.subscribe();
}private subscribe() {
this.pageLoaderService.state
.pipe(takeWhile(() => this._subscribed))
.subscribe(loading => {
this.loading = loading;
});this.pageLoaderService.message
.pipe(takeWhile(() => this._subscribed))
.subscribe(message => {
if (!!message) {
this.message = message;
}
});this.pageLoaderService.progressValue
.pipe(takeWhile(() => this._subscribed))
.subscribe(progressValue => {
if (!!progressValue) {
this.progressValue = progressValue;
this.progressValueStyle = `${progressValue}%`;
}
});
}ngOnDestroy() {
this._subscribed = false;
}
One tiny mention. If you take a peek at progressValue
subscription part, it goes like this:
Remember that [style.width]=progressValueStyle
from HTML? We set the progressValueStyle
to a valid CSS property value. In this case it can range from 0%
to 100%
. That’s how progress bar is rendered. You can dynamically attach any CSS property like this. Neat trick.
Finally, let’s implement test method to see the end result. Back in the app component:
pageLoadTest() {
let progress = 0;
this.pageLoaderService.show('Loading', 0);
const increment = setInterval(() => {
progress++;
if (progress === 100) {
this.pageLoaderService.hide();
clearInterval(increment);
}let msg =
progress < 25 ? 'Loading started' :
progress < 50 ? 'Going strong' :
progress < 75 ? 'Any minute now' :
'Almost there';
this.pageLoaderService.setMessage(msg);this.pageLoaderService.setProgressValue(progress);
}, 50);
}
I’m using interval to increment progress, and change message, just for fun. How progress is evaluated in your app, is up to you.
And that is all. You have functional, customizable page loader. Thanks for reading :)