In my Angular application I'm using a CanActivate
guard to protect the access in certain router state.
The CanActivate
guard performs a check that can last 3-4 seconds and it is very annoyng to wait that time without a loading indicator (i.e. a spinner).
Is there a "standard" way to show a loading spinner during the CanActivate check?
Thanks a lot
To answer your question, there's no "standard" way to do this.
But it's easy. I solved this by doing the following:
Import AuthGuard service to your app.component.ts
file:
import { Component, OnInit } from '@angular/core';
// Import AuthGuard service here - double check the path
import { AuthGuard } from './services/auth.guard';
@Component({
selector: 'body',
templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
// Inject AuthGuard service here
constructor(private authGuard: AuthGuard) { }
ngOnInit() { }
});
Add the loader HTML to your app.component.html
file:
<router-outlet></router-outlet>
<div class="central-loader" *ngIf="(authGuard.loader)"><div><i class="fa fa-spin fa-spinner"></i> Loading...</div></div>
Notice the *ngIf
in the loader HTML above. This checks the authGuard.loader
variable, and loads this HTML code if the value of this variable is set to true
.
In the AuthGuard file (services/auth.guard.ts
in my case):
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable, Subject } from 'rxjs';
@Injectable()
export class AuthGuard implements CanActivate {
private loader$ = new Subject<boolean>();
public loader = false;
constructor() {
this.loader$.debounceTime(300).subscribe(loader => {
this.loader = loader;
});
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
this.loader$.next(true);
// Returning an Observable for async checks
return new Observable<boolean>(obs => {
// Sample 2 second async request
setTimeout(() => {
this.loader$.next(false);
obs.next(true);
obs.complete();
}, 2000);
});
}
}
You can also directly call loader$
in the template with async
pipe, eg:
<router-outlet></router-outlet>
<div class="central-loader" *ngIf="(authGuard.loader$ | async)"><div><i class="fa fa-spin fa-spinner"></i> Loading...</div></div>
(Set loader$
to public in this case).
But I don't want this loader to show up if the canActivate check completes in less than 300ms, which is why I am subscribing instead to the loader$
with debounceTime()
.
I understand that even sending false
to the observer will be delayed by 300ms too, but that's okay for me.
By the way, this is the CSS I used for the loader, center both horizontal and vertical with full page blocking overlay:
.central-loader {
position: fixed;
display: flex;
width: 100vw;
height: 100vh;
background: rgba(0,0,0,0.2);
z-index: 1050;
}
.central-loader > div {
height: 42px;
line-height: 40px;
width: 200px;
text-align: center;
border: 2px #faf2cc solid;
color: #8a6d3b;
background-color: #fcf8e3;
margin: auto;
}
Don't forget to import AuthGuard in app.module.ts
:
import { NgModule } from '@angular/core';
....
// Import AuthGuard service here - double check the path
import { AuthGuard } from './services/auth.guard';
@NgModule({
....
providers: [
AuthGuard,
....
],
....
});
I hope this helps!
Try this, Using router events. Its simple.
<div class="loading-icon" *ngIf="loading"></div>
and in the app.component.ts
file:
this.router.events.subscribe(event => {
if (event instanceof GuardsCheckStart) {
this.loading = true;
console.log("GuardStart")
}
if (event instanceof GuardsCheckEnd || event instanceof NavigationCancel) {
this.loading = false;
console.log("GuardEnd")
}
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With