In PWA(Progressive Web Apps), each component works independently and has its lifecycle. Commonly all the components maintain their loaders/spinners.
Today, we will be looking into the implementation of a common loader (overlay spinner) which can be controlled by any component that needs the loader visible while they perform any task such as making an HTTP request. I’ve attached the Stackblitz link at the end for reference. Let’s start
The Stacks and Queue Approach
We’re gonna implement a simple queue system, any component can enqueue or dequeue their task. If the stack is empty and any task is queued we will show the loaders. And if the stack is empty, we will hide the loader. Simple, right? Let’s implement this.
Let’s say we have a component tree like this. We have a parent Component P1
then we have 2 children C1
and C2
and finally a service to communicate S1
.
Our service will have a behavior Subject. We will have 2 functions to hand queue. enqueueTaskForLoader
and dequeueTaskForLoader
and finally a variable to watch the state of stack currentStackCount
. I have used RxJs Subject to maintain the state of the loader. Here’s the final service code.
export class LoaderService {
loader$ = new BehaviorSubject<boolean>(false);
private taskCount = 0;
constructor() {}
enqueueTaskForLoader() {
if (this.taskCount == 0) {
this.loader$.next(true);
}
this.taskCount += 1;
}
dequeueTaskForLoader() {
this.taskCount -= 1;
if (this.taskCount == 0) {
this.loader$.next(false);
}
}
}
Consuming service in parent Component
Let’s start by subscribing loader subject and then using that loader to hide and show the loader, I’ve used an overlay spinner. Here’s the typescript code of parent component.
export class ParentComponent {
isLoading = false;
constructor(private loader: LoaderService) {
this.loader.loader$.subscribe((value) => {
this.isLoading = value;
});
}
}
We will use the loader variable in the template. Here’s the code for that.
<div *ngIf="isLoading" class="overlay-container">
<div class="overlay"></div>
<div class="loader"></div>
</div>
Child 1
<app-child1></app-child1>
Child 2
<app-child2></app-child2>
Consuming service in Children Components
Finally, we will enqueue and dequeue tasks in our child components. For both children, the logic is the same. Here’s the code for that
export class Child1Component {
constructor(private loader: LoaderService) {}
messsage = 'Loading Child 1';
runTask1() {
this.loader.enqueueTaskForLoader();
// Emulate a task which takes 3 seconds to complete
setTimeout(() => {
this.messsage = 'Child 1 loader after 3 seconds.';
this.loader.dequeueTaskForLoader();
}, 3000);
}
ngOnInit() {
this.runTask1();
}
}
That’s it !!!🥳 Here’ the final code link.
Here’s a gif of the loader.
Refactoring
I’ve used simple views and logic to show the loader, we can solve this in many other ways too. For example, we can use signals to maintain the loader state. Or instead of only publishing Boolean value in our subject, we can pass objects with more details like total task, failed task, successful task, and much more. I’m leaving these things in your hands 🙂
Thanks for reading. Ciao.