Ionic 4 Skip Start-Page when user is logged in

Hi all,

i have a app with a login-page.

If the user starts the app for the first time the login-page will be shown and the user has to login. If that was succesfull the app redirects the user to the menu-page (and this loads the tabs and so on).

Now, if the user closes the app or sends it to the background and reopens the app, the login-page is shown for a short period of time (less than 1 second) and it redirects again to the menu-page.

How can i prevent the login-page to be shown when the user is already logged in and the user is reopening the app? With ionic 3 that was no problem for me (using setRoot) but now with ionic 4 i face this problem (maybe because of the routesā€¦?)

Here are some code snippets from my code:

app-routing.module.ts

const routes: Routes = [
  { path: '', redirectTo: 'login', pathMatch: 'full' },
  { path: 'login', loadChildren: './pages/login/loginPage.module#LoginPageModule' },
  { path: 'menu', canActivate: [AuthGuard], loadChildren: './pages/menu/menu.module#MenuPageModule' },
]

app.component.ts

 this.splashScreen.hide();
            this.authenticationService.authenticationState.subscribe(state => {
                if (state) {
                    this.navController.navigateRoot(['menu/tabs/dashboard']);
                } else {
                    this.navController.navigateRoot('login')
                }
            });

In my AuthGuard i simply check if the user is logged in and return true or false. I followed this tutorial: https://devdactic.com/ionic-4-login-angular/

What do i have to do?

1 Like

This is how Iā€™ve implemented it (using authGuardRedirect for the root path):

const routes: Routes = [
  { path: '', redirectTo: 'home', canActivate: [LoginGuard], data: { authGuardRedirect: 'login' }, pathMatch: 'full' },
  { path: 'login', canActivate: [LoginGuard], loadChildren: './pages/login/login.module#LoginPageModule' },
  { path: 'home', canActivate: [AuthGuard], loadChildren: './pages/home/home.module#HomePageModule' },
]

Note that Iā€™m using a second Guard, named LoginGuard which checks if the ā€˜AuthGuardā€™ is true, if so, my ā€˜LoginGuardā€™ will redirect to ā€˜/homeā€™. Remove the LoginGuard in my example if not desired.

My AppComponent is (kind of) totaly empty!
My AuthGuard is handeling the redirection to ā€œrootā€ ( '/') which is subscribed to my ā€˜AuthServiceā€™ keeping track of my authentication.

Could you share the necessary parts of your LoginGuard?

Take whatever you needā€¦

AuthGuard.guard.ts

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';

import { AuthService } from '../services/auth/auth.service';

@Injectable({
  providedIn: 'root',
})
export class AuthGuard implements CanActivate {
  private loggedIn: boolean = false;

  constructor(
    private router: Router,
    private authService: AuthService,
  ) {
    this.authService.getAuthentication().subscribe((value: boolean) => {
      this.loggedIn = value;

      if (this.loggedIn) {
        this.router.navigate(['/']);
      } else {
        this.router.navigate(['logout']);
      }
    });
  }

  public canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    if (!this.loggedIn) {
      return this.router.navigate(['login']);
    }

    return this.loggedIn;
  }

  public getGuardAuthentication(): boolean {
    return this.loggedIn;
  }
}

LoginGuard.guard.ts

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthGuard } from './auth.guard';

@Injectable({
  providedIn: 'root',
})
export class LoginGuard implements CanActivate {

  constructor(
    private router: Router,
    private authGuard: AuthGuard,
  ) { }

  public canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    if (this.authGuard.getGuardAuthentication()) {
      this.router.navigate(['home']);
    }
    return true;
  }
}

AuthService.service.ts

  private authenticated: BehaviorSubject<boolean> = new BehaviorSubject(false); // starting app default as unauthorised

    ...

  constructor(
    ...
    private platform: Platform,
    ...
  ) {
    this.platform.ready().then(async () => {
      const accesstoken = YOUR_STORAGE_GET_ITEM('accesstoken');
      return await this.authenticated.next(accesstoken ? true : false);
    });
  }

    ...

  public getAuthentication(): Observable<boolean> {
    return this.authenticated.asObservable();
  }

As youā€™ve also did read on the other ā€˜login threadā€™, because of the initial ā€˜falseā€™ value of the observable you might indeed see the login page for a split second.
To fix that Iā€™ve implemented my this.splashScreen.hide(); with a 500ms timeout on the only 2 possible landing pages (ā€˜homeā€™ and ā€˜loginā€™).

F.y.i.: I can live with the timeout solution : )

If anyone has a better sollution please do SHARE!

I tried to implement this.splashScreen.hide(); with a 500ms timeout on my Login and Dashboard-page but this seems not to be working.

There has to be a simpler solution to prevent the app to show the Login-Page at startup - or maybe i just donā€™t understand :smiley:

I have solved my problems! Now the app starts and when the user is already logged in my dashboard-page will be shown without the Login-page being shown for less than 1 sec.

my app-routing.module

  { path: '', redirectTo: 'menu/tabs/dashboard', pathMatch: 'full' },
  { path: 'login', loadChildren: './pages/login/loginPage.module#LoginPageModule' },
  { path: 'menu', canActivate: [AuthGuard], loadChildren: './pages/menu/menu.module#MenuPageModule' },

my app.component.ts

 //Check if user is logged-in; if so the dashboard will be loaded otherwise the login-page
            this.authenticationService.authenticationState.subscribe(state => {
                if (state) {
                    console.log("user is logged in");
                    this.navController.navigateRoot(['menu/tabs/dashboard']);
                    this.splashScreen.hide();
                } else {
                    console.log("user is NOT logged in");
                    this.navController.navigateRoot('login');
                    this.splashScreen.hide();
                }
            });

my authenticationService.service.ts

export class AuthenticationService {

  authenticationState = new BehaviorSubject(false);

  constructor(private storage: Storage,
              private platform: Platform,
              public global: GlobalProvider) {
    this.platform.ready().then(async () => {
      this.checkToken();
    });
  }

  checkToken() {
    if (this.global.getUserData() || this.global.getUserData() !== null){
      this.authenticationState.next(true);
    }
  }

  login() {
      this.authenticationState.next(true);
  }

  logout() {
      localStorage.clear();
      this.authenticationState.next(false);
  }

  isAuthenticated() {
    return this.authenticationState.value;
  }

}

my auth.guard.ts

export class AuthGuard implements CanActivate {

    constructor(public authenticationService: AuthenticationService) {
    }

    canActivate(): boolean {
        return this.authenticationService.isAuthenticated();
    }

}

When the user is being logged in i set some data into the localstorage. In my authService i look for these data and set the state to true or false. In the app.component.ts i subscribe to this state and switch the root navigation.

4 Likes

still showing login page for 1 second. see authenticationState has initial value as false.
authenticationState = new BehaviorSubject(false);
so itā€™s going to the login and once authenticationState got true itā€™s going to the home.

What is the GlobalProvider you used in this example?

Just a custom class where i do some stuff like setting and getting user data.

1 Like

oki thanks.i was fixed that issue using ReplaySubject instead of BehaviorSubject.BehaviorSubject is required initial value.so thatā€™s the case login page shows for 1 second.

Hi shorstmann donā€™t worry I am here to help you, below is a sample code your help, just add it inside your app.component.ts file donā€™t forget to vote the answer

initializeApp() {
this.platform.ready().then(() => {
this.statusBar.styleDefault();
this.splashScreen.hide();
this.db.createDatabase()
this.nativeStorage.getItem(ā€œuserā€).then(res => {
this.router.navigate(["/home"], { skipLocationChange: true });
}).catch(error => {
this.router.navigate(["/login"], { skipLocationChange: true });
});
});
}

cheers!!

2 Likes

thq, its worked for meā€¦

Sorry, it is not helpful.

hi shorstmann have you did it in app.component.ts file? if yes then show me your code,

hey ishaquemujtaba in my case its still showing for 1 second

here is my code

initializeApp() {
this.platform.ready().then(() => {
this.statusBar.styleDefault();
this.splashScreen.hide();

  this.auth.afAuth.authState.subscribe((state)=>{
    if (state) {
      console.log("user is logged in");
      // this.navController.navigateRoot(['tabs']);
      this.router.navigate(["/tabs"], { skipLocationChange: true });
  } else {
      console.log("user is NOT logged in");
      this.router.navigate(["/login"], { skipLocationChange: true });
      // this.navController.navigateRoot('login');
  }
    
  });
});

}

One of the things that you have to accept when writing reactive webapps (which all Ionic apps are) is that you cannot know and should not try to influence when things happen. Thatā€™s sort of baked into the word ā€œreactiveā€ in the first place. You put things into certain states, wait for triggers to happen, and then react.

In this case, the process of discovering whether a user has already been authenticated is one of those things with unpredictable timing. Instead of trying to eliminate that ā€œ1 secondā€, I would suggest instead putting the app into a state for however long it takes that makes sense for either case (authenticated or not).

The most straightforward way I would think to try would be to delay hiding the splash screen until inside your subscription. If that isnā€™t to your liking, how about creating your own ā€œsplash pageā€, navigate to that immediately, and then later on to /tabs or login as appropriate?

2 Likes

just do it inside your routing class and it will be resolved

As rapropos did made it clear, whatever you do, one way around, youā€™ll have to give the app time to initialize. So the timeout / delay solution should be a proper solution. Just donā€™t forget that it will only work ā€˜correctā€™ when running on an device and not in your browser. (no splashscreen in browser!)

Hi, so what is the best way to tackle the 1 second show up of the login form? Increase timeout/duration of splashscreen?

Do it like i did. No timeout needed.