Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Show loading indicator during CanActivate

Tags:

angular

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

like image 991
user3471528 Avatar asked Jan 28 '18 13:01

user3471528


2 Answers

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>&nbsp; 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>&nbsp; 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!

like image 160
James A Avatar answered Sep 24 '22 09:09

James A


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")
      } 
    });
like image 38
dp_t Avatar answered Sep 25 '22 09:09

dp_t