App Navigation handled by the authentication


#1

I’ve try to handle the navigation by the check of current authentication of user. I’ve use a Observable in my AuthProvider to check user authentication status and

I’ve use this to set the root page in my app.component.ts.

export class MyApp {
rootPage: any = LoginPage;
  isLoggedIn$: Observable<boolean>;
enter code here
constructor(platform: Platform,
statusBar: StatusBar,
splashScreen: SplashScreen,
private authService: AuthServiceProvider) {

this.isLoggedIn$ = authService.isLoggedIn();
platform.ready().then(() => {
  //check user logged
  this.isLoggedIn$.subscribe(res => {
    if (res) {
      this.rootPage = TabsPage; //Home page if user is logged in
    } else {
      this.rootPage = LoginPage; //Login page if user isn't logged id
    }
  });        

  statusBar.styleDefault();
  splashScreen.hide();
});
}

The check function isLoggedIn() in my authProvider is

  isLoggedIn(): Observable<boolean> {
return this.user.map((auth) => {
  if (auth == null) {
    return false;
  } else {        
    return true;
  }
});
}

I’ve use Firebase and AngularFireAuth to handle the login and logout process. The logout function in my AuthProvider is…

logout() {

this.afAuth
  .auth
  .signOut();
}

This code work very fine if I access in my app and I never use…

this.navController.push(ChildPage) //it work fine but cause error in next logout-login fase

or

this.navController.push('ChildPage') //it work fine but cause error in next logout-login fase

Ex. I haven’t problems if I use TabController for navigation.

Steps to get Error:

User is unauthenticated and open the app

  1. LoginPage -> login(credential) -> HomePage
  2. HomePage -> goToChildPage(page){this.navController.push(ChildPage);} -> ChildPage
  3. ChildPage -> authProvider.logOut() -> LoginPage
  4. LoginPage -> login(credential) -> DESPERATION :frowning:
  5. In the fifth step I recive this error:
ERROR TypeError: Cannot read property 'push' of null
at Tab.NavControllerBase._queueTrns (VM5768 vendor.js:51240)
at Tab.NavControllerBase.push (VM5768 vendor.js:51128)
at SafeSubscriber._next (VM5769 main.js:214)
at SafeSubscriber.__tryOrUnsub (VM5768 vendor.js:22842)
at SafeSubscriber.next (VM5768 vendor.js:22789)
at Subscriber._next (VM5768 vendor.js:22729)
at Subscriber.next (VM5768 vendor.js:22693)
at MapSubscriber._next (VM5768 vendor.js:124303)
at MapSubscriber.Subscriber.next (VM5768 vendor.js:22693)
at SwitchMapSubscriber.notifyNext (VM5768 vendor.js:135509)

Can someone help me please?

Thank you :slight_smile:


#2

First things first, You have to remove initialization statement from rootPage: any = LoginPage to rootPage: string;.
As you’ve mentioned that you’re using firebase, how about using onAuthStateChanged method to set the observer on the Auth object. Here’s my example code below.
It’s always a good idea to read the documentation.

export class MyApp {
  rootPage: string; 

  constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen) {
   
    const unsubscribe = firebase.auth().onAuthStateChanged(user => {
      if (user) {
        this.rootPage = 'TabsPage'; 
        unsubscribe();
      } else {
        this.rootPage = 'LoginPage';
        unsubscribe();
      }
    });

    platform.ready().then(() => {
      // Okay, so the platform is ready and our plugins are available.
      // Here you can do any higher level native things you might need.
      statusBar.styleDefault();
      splashScreen.hide();
    });
  }
}

#3

Thank you Jeff,

I’ve just try your suggestion in app.component.ts

import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import { Observable } from 'rxjs/Observable';
import { TabsPage } from '../pages/tabs/tabs';
import { LoginPage } from '../pages/login/login';
import { AuthServiceProvider } from '../providers/auth-service/auth-service';
import { AngularFireAuth } from 'angularfire2/auth';


@Component({
  templateUrl: 'app.html'
})
export class MyApp {
  rootPage: string;
  isLoggedIn$: Observable<boolean>;

  constructor(platform: Platform,
    statusBar: StatusBar,
    splashScreen: SplashScreen,
    private authService: AuthServiceProvider,
    private afAuth: AngularFireAuth) {

    const unsubscribe = afAuth.auth.onAuthStateChanged(user => {
      if (user) {
        this.rootPage = 'TabsPage';
        unsubscribe();
      } else {
        this.rootPage = 'LoginPage';
        unsubscribe();
      }
    });
    
    platform.ready().then(() => {     

      statusBar.styleDefault();
      splashScreen.hide();
    });
  }
}

but now, I have this error:

vendor.js:1703 ERROR Error: Uncaught (in promise): invalid link: TabsPage
    at c (polyfills.js:3)
    at Object.reject (polyfills.js:3)
    at NavControllerBase._fireError (vendor.js:51282)
    at NavControllerBase._failed (vendor.js:51275)
    at vendor.js:51322
    at t.invoke (polyfills.js:3)
    at Object.onInvoke (vendor.js:4982)
    at t.invoke (polyfills.js:3)
    at r.run (polyfills.js:3)
    at polyfills.js:3

Can you help me?
Thank you.


#4

Change

rootPage: string;

To

rootPage: any;

Also, remove the strings from TabsPage.

this.rootPage = TabsPage;

It’s an invalid link because it’s (i’m assuming) not lazy-loaded. Only reference pages by putting strings around them if they are lazy-loaded ( have their own module.ts ) So you may have to remove the string from LoginPage as well. From what i see in your post, you’ve noticed different behavior based on whether or not a page is referenced with strings, and that is why.

lazy-loaded: reference with strings, and you don’t have to import into the component your referencing it in.

this.navController.push('ChildPage');

not lazy-loaded: import it into the component you’re referencing it in, and do not put strings around it.

this.navController.push(ChildPage);

Also, move your auth check into platform.ready

platform.ready().then(() => {
let unsubscribe = afAuth.auth.onAuthStateChanged(user => {
      if (user) {
        this.rootPage = TabsPage;
        unsubscribe();
      } else {
        this.rootPage = LoginPage;
        unsubscribe();
      }
    });
})

You want to put all the stuff that’s in app.component.ts in platform.ready for the most part. Auth checks, initilaizing any plugins, etc.


#5

@jaydz explained it very well.
Depending on the size of your application and some other factors you should consider the style of lazy loading that you want to use.
If you have a substantial amount of components then the best way would be a singular module. If you only have a few and you want to keep things simple by only importing the components module then a shared module. :slight_smile: