Ionic 2 Hide menu or tabs for login screen

A question that often comes up in the Ionic forums is how to hide the menu or tabs for a login screen. In Ionic 1 you just made a view that wasn’t a child of the menu view in the Angular UI router. Here the idea is actually mostly the same, you just use navs and the NavController instead of the UI router. We’ll change the app module to no longer contain the menu, instead containing only a nav component, then the menu component will be added to that nav if the user is logged in. If the user is not logged in we’ll stack the login screen on there as well. What follow is a detailed and somewhat long walkthrough, but the concept is simple, don’t give the core navigation any UI at all, then you can stack any UI on top of it, menu, tabs, neither, it doesn’t matter.

Please check out the complete project on github: https://github.com/roblouie/login-screen-demo

For this example I’ll be starting with the sidemenu starter project. The idea is identical when using tabs though.

Core goals:

  • Use a modular approach so our code is reusable in many apps
  • Along those lines, keep knowledge of the rest of the app out of the login module
  • When launching the app don’t even show a flash of the menu, go straight to the login screen
  • Follow the Angular Style Guide as closely as possible in Ionic

A new app component

By default in the sidemenu starter app, the file app.component.ts contains the menu itself. This is actually the root of our issue, because that means any pages added to the nav stack will added to the nav in the menu. So we’re going to change that. Create two new files in your src/app/ folder, menu.component.ts, and menu.html. Cut and paste directly the contents of app.component.ts and app.html into these new files. That means app.component.ts is emtpy, and app.html is empty, and the new menu files contain their old info. This new menu component will no longer be the root, so remove the initialize app method. Your final menu.component.ts should look like this:

import { Component, ViewChild } from '@angular/core';
import { Nav } from 'ionic-angular';

import { Page1 } from '../pages/page1/page1';
import { Page2 } from '../pages/page2/page2';

@Component({
  templateUrl: 'menu.html'
})
export class MenuComponent {
  @ViewChild('content') nav: Nav;

  rootPage: any = Page1;

  pages: Array<{title: string, component: any}>;

  constructor() {
    this.pages = [
      { title: 'Page One', component: Page1 },
      { title: 'Page Two', component: Page2 }
    ];
  }

  openPage(page) {
    // Reset the content nav to have just this page
    // we wouldn't want the back button to show in this scenario
    this.nav.setRoot(page.component);
  }
}

Notice this is exactly the same as the old app.component.ts, except no initialize app, and we’ve renamed it from “MyApp” to “MenuComponent”. Menu.html remains the same as the old app.html.




Since this is a new component, you need to add it to the declarations and entryComponents in app.module.ts.

@NgModule({
  declarations: [
    MyApp,
    Page1,
    Page2,
    MenuComponent
  ],
  imports: [
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    Page1,
    Page2,
    MenuComponent
  ],

Now lets create our new app.component.ts. I’ll show the code below then we’ll talk about it:

import { Component, ViewChild } from '@angular/core';
import { Nav, Platform } from 'ionic-angular';
import { StatusBar, Splashscreen } from 'ionic-native';

import { MenuComponent } from "./menu.component";

@Component({
  template: '<ion-nav #baseNav></ion-nav>'
})
export class MyApp {
  @ViewChild('baseNav') nav: Nav;

  constructor() {
    this.initializeApp();
  }

  ngOnInit() {
    this.nav.push(MenuComponent, { animate: false });
  }

  initializeApp() {
    this.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();
    });
  }
}

Okay, so let’s talk about what we have here. Instead of templateUrl, we just do an inline template, because it’s only a one-liner. A lowly ion-nav element with nothing else :(. Notice the nav identifier has been named baseNav just for clarification that this is our base. If you were to run this without the ngOniInit section you’d just get an empty screen, but let’s talk about the ngOnInit question.

ngOnInit runs when the component is initialized, and that’s important because we need to grab the nav off the screen. In the starter app it sets the root component, but we’re taking a slightly different approach here. The reason will become clear in the next section. As a side note, since the template is inlined, you can delete your old app.html.

If you run the app now, you should notice that it looks exactly the same as the starter sidemenu app, the sidemenu component, because the MenuComponent is added to the nav stack. If you don’t see that hit that comments section and I’ll help you through. On to the next step.

The Login Module

Create a new folder in src/app/ named “login”. Within that folder, create the following files: login.component.ts, login.html, login.module.ts, and login.service.ts. Let’s start with login.service.ts, since that will contain the business logic. Here’s we really just mock out a fake login system:

import { Injectable } from '@angular/core';

@Injectable()
export class LoginService {
  private _username: string;

  login(username) {
    this._username = username;
  }

  logout() {
    this._username = '';
  }

  get isLoggedIn(): boolean {
    return this._username in this;
  }

  get username(): string {
    return this._username;
  }
}

In reality you’ll most likely doing a call to a web service, as well as maybe checking for a token stored locally, but here we just store a username. Now let’s create the login component:

import { Component } from '@angular/core';
import { LoginService } from "./login.service";
import {NavController} from "ionic-angular/index";

@Component({
  templateUrl: 'login.html'
})
export class LoginComponent {

  constructor(private loginService: LoginService, private nav: NavController) {}

  login(username) {
    this.loginService.login(username);
    this.nav.pop();
  }
}

We’re importing the login service, and calling it in a new login() function passing in the username. We then pop this off the nav because once the user logs in, we want to remove the login screen. You can see how this links to the login.html here:

<ion-content padding>
  <h3>Login</h3>

  <p>Enter a username below then click login.</p>

  <input type="text" #username />

  <button ion-button secondary (click)="login(username.value)">Login</button>
</ion-content>




Okay, now let’s just pull this all together in login.module.ts:

import { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';

import { LoginComponent } from "./login.component";

@NgModule({
  imports: [IonicModule],
  declarations: [LoginComponent],
  entryComponents: [LoginComponent]
})
export class LoginModule {}

Now import our new module into the main app module in it’s import section.

app.module.js:

imports: [
  IonicModule.forRoot(MyApp),
  LoginModule
],

With our new module ready to go, we need to revisit our app.component.ts file. We need to inject our new LoginService here, check if the user is logged in or not, and show the proper screen:

@Component({
  template: '<ion-nav #baseNav></ion-nav>',
  providers: [LoginService]
})
export class MyApp {
  @ViewChild('baseNav') nav: Nav;

  constructor(public platform: Platform, private loginService: LoginService) {
    this.initializeApp();
  }

  ngOnInit() {
    const componentStack: Array<{page: Component}> = [{
      page: MenuComponent
    }];

    if (!this.loginService.isLoggedIn) {
      componentStack.push({ page: LoginComponent });
    }

    this.nav.insertPages(0, componentStack, { animate: false });
  }

Notice we’ve declared the LoginService under providers and injected it in to the constructor. Since app.component.ts is the root of all navigation, declaring the provider here makes the same instance accessible to all child components. The most important thing here is the change to ngOnInit(). What we’re doing here is actually pushing two pages onto the nav if the user isn’t logged in. This stops the menu screen from flashing to the user. The nav controller has a method called insertPages that lets you add multiple pages simultaneously. So we always put the MenuComponent on the stack, but only put the LoginComponent on top of it if the user isn’t logged in. Then both are added to the nav simultaneously, hiding any flash of the menu content.

Summary

And that does it. If you run into any issues or have questions, please comment below. Also, please check out the full project on github, which also includes a logout button and showing the username: https://github.com/roblouie/login-screen-demo