Search code examples
angularionic-frameworkionic4angular-router

App Routing method when login is required causing white screen and login screen to appear on each load


I am trying to device my strategy for the best way to route through my app, knowing that a person needs to login in order to access a bulk of the app. Right now, my routes are setup like this:

const routes: Routes = [
  {
    path: '',
    redirectTo: '/app/tabs/home',
    pathMatch: 'full',
    canActivate: [AuthGuard]
  },
  {
    path: 'app',
    loadChildren: () => import('./pages/tabs/tabs.module').then(m => m.TabsPageModule),
    canActivate: [AuthGuard]
  },
  {
    path: 'login',
    loadChildren: () => import('./pages/login/login.module').then( m => m.LoginPageModule)
  },
];

I am running into a problem that each time my app loads (and you have previously authenticated), there is a slight delay in the loading, which causing a white screen to appear, then my login screen appears, then you are routed to the correct destination. Currently, I am using an AuthGuard on the routes, and I use an Obersvable to see if a user has been authenticated, and if not, then redirect them to the login.

this.auth.isAuthenticated.subscribe((value) => {
   if (!value) {
     return this.router.navigate(['/login']);
   }
});

I believe this may have something to do with some items being loading out of sequence.

My auth service is loaded in my app.component.ts as the platform loads:

this.platform.ready().then(() => {
   this.statusBar.styleLightContent();
   this.splashScreen.hide();
   this.auth.load();
});

And in my auth.service.ts, this is the load function:

load() {
  this.isAuthenticated.next(false);
  this.getToken().then(token => {
    if (token && token !== '') {
      this.isAuthenticated.next(true);
    } else {
      this.router.navigateByUrl('/login');
    }
  });

Any thoughts on where I should be looking at what is causing the load sequence issue and why my login screen flashes up on each load of the app? }

EDIT

I believe the issue my be a symptom the isAuthenticated BehaviorSubject in my Auth Service.

I changed my AuthGuard code to:

this.auth.isAuthenticated.subscribe((value) => {
        if (!value) {
          console.log("failed");
          this.router.navigate(['/login']);
          return false;
        }
      });

      return true;

And each time the app reloads, I am seeing the failed message in the console. Is something loading out of sequence with the Observable?


Solution

  • If your requirement is that you want to check the user authentication before hiding the splash screen, I suggest moving the splash screen hide code into your auth.load() method

    this.platform.ready().then(() => {
       this.statusBar.styleLightContent();
       // this call will be moved into your auth
       //this.splashScreen.hide();
       this.auth.load();
    });
    
    // init with null
    isAuthenticated = new BehaviorSubject<boolean>(null);
    
    load() {
       // don't set it to false..
       // wherever you initialize it, set it to null for default value
      // this.isAuthenticated.next(false);
      this.getToken().then(token => {
        if (token && token !== '') {
          this.isAuthenticated.next(true);
        } else {
          this.isAuthenticated.next(false);
          // this navigate probably isn't needed since you are doing it in your guards already, but I don't have all of your code to determine that
          this.router.navigateByUrl('/login');
        }
        this.splashScreen.hide();  // you'll need to import it in your service for it to work
      }).catch(error => {
        // also, in case there is an error you probably also want to hide the splash screen
        this.splashScreen.hide();
    });
    }
    

    Also, you are setting isAuthenticated.next(false) when you load so your route guard will always fail and redirect to the loading screen.

    If isAuthenticated is a BehaviorSubject you can initialize it to null and in your route guard check if its null and wait for an explicit true or false value before redirecting.

    Updated Auth Guard

    import { Router } from '@angular/router';
    
    constructor(private auth: SomeAuthService, private router: Router) {}
    canActivate(
        next: ActivatedRouteSnapshot,
        state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
        return this.auth.isAuthenticated.pipe(
           // skip initial "null" value, because that means we didn't finish our auth check
           filter(val => val !== null)),
           // now we will map the true/false response here
           map(isAuthenticated => {
             if (isAuthenticated) {
               return true
             } else {
                // build a URLTree pointing to /login
               // you need to import Router from angular 
                return this.router.parseUrl("/login")
             }
           })
    
    
      }