Adding a login page to an Ionic sidemenu project

September 19, 2017, 11:00 am Categories:

Categories

In this tutorial I'll show you how to add a login screen to a sidemenu project layout so that the sidemenu is only visible AFTER a user has successfully logged in.

Along the way we'll also be integrating a simple Firebase authentication service and making use of Angular's FormBuilder API.

What to expect

By the end of the tutorial you should be able to launch the application in your desktop browser and see the following login form:

Ionic e-mail/password log-in form

Upon successful authentication with the Firebase service you will then be redirected to the application's sidemenu layout like so:

Ionic side menu displayed inside logged in state of application

The logged-in part of the application will remain as the default generated views as the sole purpose of this tutorial is to demonstrate how to integrate a log-in mechanism with a sidemenu based layout.

Notice the logout option at the bottom of the sidemenu? We'll use this to redirect the application back to the login page - using a combination of Firebase and Ionic's NavController API.

Now we know what we're building let's get started...

First things first

As always it's important that your system environment has the necessary software installed (I.e. Node, Ionic CLI etc) and is configured for Ionic applications to be developed - additionally you'll also need to have your own Firebase account ready for use.

Assuming these are in place the project we'll be creating will make use of the following:

  • Firebase authentication
  • Angular FormBuilder API
  • Angular Services

By the end of this tutorial the completed application will be run using your default desktop browser (I.e. Safari, Chrome, Firefox etc) so there won't be a need to generate build packages for iOS and/or Android.

Setting up Firebase

Navigate to the Firebase website in your browser and, if already registered with the service, log in to your account (if you're not registered then go ahead and create an account - it's free!)

Once logged in you should see all of your existing projects (if you have any) displayed on your account home page like so:

Firebase projects landing page

Let's begin creating a brand new project by selecting the CREATE NEW PROJECT button and, in the modal window that appears, entering your project name (name this whatever you feel works best) and selecting the Country/region from the drop down menu:

Firebase create project

Once your project has been created you can choose how to add Firebase to your application by simply choosing from the following options:

  • Add Firebase to your iOS app
  • Add Firebase to your Android app
  • Add Firebase to your web app

Go ahead and select the Add Firebase to your web app option as this will provide us with the necessary configuration values to integrate Firebase into our Ionic application regardless of platform (I.e web, iOS or Android):

Choose how Firebase will be added to your app

From the modal window that appears, copy the following configuration keys and values:

  • apiKey
  • authDomain
  • databaseURL
  • storageBucket
  • messagingSenderId

Simply copy but do NOT edit these values as you WON'T be able to connect with your Firebase app without them:

Our completed Firebase database

Once copied paste these configuration keys/values into a text file for safekeeping (we'll subsequently enter these into an environment.ts file once we've created our Ionic project from the command line).

Next we need to configure the authentication options for the project.

From the left-hand menu in the Firebase console select Authentication.

Click onto the SIGN-IN METHOD tab on the Authentication screen and, from the listed Sign-in providers, select the E-mail/Password option:

Firebase authentication options

Enable this authentication option:

Enabling e-mail authentication for Firebase

Once saved select the USERS tab, click onto the ADD USER button and enter your e-mail/password for this project:

Adding an email account for Firebase authentication

And that's all we'll need as far as Firebase configuration is concerned!

With the Firebase aspect of our project in place we can now move onto the next step in the tutorial - creating our Ionic project.

Setting up the project codebase

Open up your command line software, navigate to where you store digital projects on your system, and issue the following instruction to create a new Ionic project - imaginatively titled sidemenu-demo - using the sidemenu template option:

ionic start sidemenu-demo sidemenu

Once completed, change into the project and generate the following resources:

ionic g page login
ionic g provider auth

Followed by installing the necessary node packages that will allow us to make use of the Firebase Web API (and also avoid any polyfill errors that might be thrown up during project build processes):

npm install --save firebase
npm install --save promise-polyfill

Next you'll want to create a new folder titled environments in your sidemenu-demo/src directory that contains a single typescript file - environment.ts - with your Firebase Web API configuration keys/values like so:

export const environment = {
   production: false,
   firebase : {
      apiKey 				: "PASTE_YOUR_FIREBASE_API_KEY_HERE",
      authDomain			: "PASTE_YOUR_FIREBASE_DOMAIN_HERE",
      databaseURL			: "PASTE_YOUR_FIREBASE_DATABASE_URL_HERE",
      projectId				: "PASTE_YOUR_FIREBASE_PROJECT_ID_HERE",
      storageBucket			: "PASTE_YOUR_FIREBASE_STORAGE_BUCKET_HERE",
      messagingSenderId		: "PASTE_YOUR_FIREBASE_MESSAGING_SENDER_ID_HERE"
   }
};

Once completed double check that this file resides within the following location: sidemenu-demo/src/environments/environment.ts.

Next we need to make some amendments to the application's root module - src/app/app.module.ts - so that the HttpModule is declared in the imports section (WHY this isn't automatically declared when a provider is generated through the Ionic CLI I don't know but, currently, if the HttpModule isn't imported and declared an error is thrown during build processes).

We also need to import the src/environments/environment.ts file we recently created and use this to supply credentials for initialising firebase within our root module (FYI: if we try to import this into the application's root component - src/app/app.component.ts - we end up with initialisation errors which is why we use the root module instead).

Putting these amendments in place your application's root module should resemble the following:

import { BrowserModule } from '@angular/platform-browser';
import { HttpModule } from '@angular/http';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';

import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { ListPage } from '../pages/list/list';
import { LoginPage } from '../pages/login/login';

import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import { AuthProvider } from '../providers/auth/auth';

// Import Firebase / environment config and initialise
import * as firebase from 'firebase';
import { environment } from '../environments/environment';

firebase.initializeApp(environment.firebase);


@NgModule({
  declarations: [
    MyApp,
    HomePage,
    ListPage,
    LoginPage
  ],
  imports: [
    BrowserModule,
    HttpModule,
    IonicModule.forRoot(MyApp),
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage,
    ListPage,
    LoginPage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    AuthProvider
  ]
})
export class AppModule {}

Finally you'll need to amend the root component for the project - sidemenu-demo/src/app/app.component.ts - so that the following changes are made:

  • The LoginPage component is imported
  • The rootPage value is changed from HomePage to LoginPage
  • Add functionality to log out of firebase and return the user back to the LoginPage component

Once implemented these changes should result in a root component file that looks like this (code formatting and commenting added by myself to improve readability of file):

import { Component, ViewChild } from '@angular/core';
import { Nav, Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';

// Import page components and authentication provider
import { HomePage } from '../pages/home/home';
import { ListPage } from '../pages/list/list';
import { LoginPage } from '../pages/login/login';
import { AuthProvider } from '../providers/auth/auth';



@Component({
  templateUrl: 'app.html'
})
export class MyApp {
   @ViewChild(Nav) nav: Nav;


   /**
    * Set the root page for the application
    */
   public rootPage: any = LoginPage;



   /**
    * Define the pages for the application
    */
   private pages: Array<{title: string, component: any}>;



   constructor(public platform      : Platform, 
               public statusBar     : StatusBar, 
               public splashScreen  : SplashScreen,
               private _AUTH        : AuthProvider) 
   {
      this.initializeApp();
 

      // Populate pages for the application
      this.pages = [
        { title: 'Home', component: HomePage },
        { title: 'List', component: ListPage },
        { title: 'Logout', component: LoginPage }
      ];
   }




   /**
    * Initialise the application
    * @method initializeApp
    * return {none}
    */
   initializeApp() : void
   {
      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.
         this.statusBar.styleDefault();
         this.splashScreen.hide();
      });
   }




   /**
    * Open a page from the sidemenu
    * @method openPage
    * @param page   {object}      The name of the page component to open
    * return {none}
    */
   openPage(page : any) : void
   {
      // Ensure we can log out of Firebase and reset the root page
      if(page == 'Logout')
      {
         this._AUTH.logOut()
         .then((data : any) =>
         {
            this.nav.setRoot(page.component);
         })
         .catch((error : any) =>
         {
            console.dir(error);
         });       
      }

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

With the foundational elements for our project now in place let's move onto configuring the necessary methods for the Firebase authentication service.

Defining project authentication

In the Setting up Firebase section we defined the sign-in provider to use for this project - which involves supplying an e-mail/password value for our account.

In order to authenticate access for our project we'll make use of a small subset of the auth() service methods supplied by the Firebase Web API.

These will be crafted into our own custom authentication methods that we'll shortly add within the src/providers/auth/auth.ts service.

By placing these custom methods within a service we ensure that our authentication logic will be delivered from one file and not scattered/repeated over multiple components - which makes for sensible and effective code management (particularly if the project should grow/be accessed by other developers).

The Auth service is fairly minimal (as we're only using a small subset of the available methods supplied by the Firebase auth() class) and simply handles the following requirements:

  • Authenticates the user with the signInWithEmailAndPassword method
  • Logs the user out from Firebase

Pretty simple but effective in meeting the needs of this project - as demonstrated in the src/providers/auth/auth.ts service below:

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import { Observable } from 'rxjs/Observable';

// DON'T forget to import the Firebase node package!
import * as firebase from 'firebase';



@Injectable()
export class AuthProvider {

   public user                   : Observable;

 
   constructor(public http       : Http) 
   {
      firebase.auth().onAuthStateChanged((user) =>
      {
        if (user) 
        {
          // User is signed in.
          console.log('User is signed in');
        } 
        else 
        {
          // No user is signed in.
          console.log('User is NOT signed in');
        }
      });
   }




   /** 
    * Use Firebase Web API signInWithEmailAndPassword method 
    * to authenticate user login attempt
    * 
    * @method loginWithEmailAndPassword
    * @param email    {string}      User e-mail address
    * @param password {string}      E-mail account password
    * @return {Promise}
    */
   loginWithEmailAndPassword(email     : string, 
                             password  : string) : Promise
   {      
      return new Promise((resolve, reject) =>
      {
         firebase
         .auth()
         .signInWithEmailAndPassword(email, password)
         .then((val : any) =>
         {
            resolve(val);
         })
         .catch((error : any) =>
         {
            reject(error);
         });
      });
   }



   
   /**
    * Log out with Firebase Web API signOut method 
    * 
    * @method logOut
    * @return {Promise}
    */
   logOut() : Promise
   {
      return new Promise((resolve, reject) =>
      {
        firebase
        .auth()
        .signOut()
        .then(() =>
        {
           resolve(true);
        })
        .catch((error : any) =>
        {
           reject(error);
        });
      }); 
   }

}

These should be fairly simple and straightforward to understand (the method names and commenting should guide you if you're unsure as to what each section of the above service is designed to do) so we can now move onto configuring the LoginPage component.

Logging in

As the LoginPage component is the sole point of access for our project we'll need to implement an e-mail/password sign-in form that makes use of the loginWithEmailAndPassword method from our AuthProvider service. In doing so we'll also take advantage of Angular's FormBuilder class to ensure that the sign-in form is unable to be submitted until the e-mail/password fields have been completed.

In full then (and it's actually quite a minimal amount of code we're adding to the class) our sidemenu-demo/src/pages/login/login.ts component should resemble the following:

import { Component } from '@angular/core';
import { IonicPage, NavController } from 'ionic-angular';
import {
   FormBuilder,
   FormGroup, 
   Validators } from '@angular/forms';

import { AuthProvider } from '../../providers/auth/auth';

// Import the TabsPage component
import { TabsPage } from '../tabs/tabs';

@IonicPage()
@Component({
  selector: 'page-login',
  templateUrl: 'login.html',
})
export class LoginPage {
  
   /**
    * Create reference for FormGroup object
    */
   public form                  : FormGroup;
   

   constructor(public navCtrl    : NavController,
               private _FB       : FormBuilder,
               private _AUTH     : AuthProvider) 
   {
      // Define FormGroup object using Angular's FormBuilder
      this.form = this._FB.group({
         'email'        : ['', Validators.required],
         'password'     : ['', Validators.required]
      }); 
   }




   /**
    * Log in using the loginWithEmailAndPassword method 
    * from the AuthProvider service (supplying the email 
    * and password FormControls from the template via the 
    * FormBuilder object
    * @method logIn
    * @return {none}
    */
   logIn() : void
   {
      let email      : any        = this.form.controls['email'].value,
          password   : any        = this.form.controls['password'].value;

      this._AUTH.loginWithEmailAndPassword(email, password)
      .then((auth : any) => 
      {
         this.navCtrl.setRoot(TabsPage);
      })
      .catch((error : any) => 
      {
         console.log(error.message);
      });
   }

}

We could (and should) enhance the handling of rejected promises in the logIn method by utilising something along the lines of Ionic's AlertController class to display an error dialog on the screen should Firebase authentication fail.

I'll leave this to you to experiment with adding to the project :)

In the corresponding HTML template for the component - src/pages/login/login.html - we'll add the necessary templating for the form (along with an Ionic brand logo) which makes use of Ionic's pre-built components and integrates in with the FormBuilder object created in the component's class:

<ion-header>
   <ion-navbar>
      <ion-title>
         Demo
      </ion-title>
   </ion-navbar>
</ion-header>

<ion-content padding>
   

   <img 
      class="logo"
      src="assets/images/icon.png"
      alt="Ionic Framework logo">


   <div
      class="login">

      <h1>Welcome</h1>

      <p>Please log in to access the Demo.</p>

      <form 
        [formGroup]="form" 
        (ngSubmit)="logIn()"> 
        <ion-list>
            <ion-item margin-bottom> 
               <ion-label>Your E-mail Address</ion-label> 
               <ion-input
                  type="email"
                  formControlName="email"></ion-input> 
            </ion-item>
   
   
            <ion-item margin-bottom>
               <ion-label>Your Password</ion-label> 
               <ion-input
                  type="password"
                  formControlName="password"></ion-input> 
            </ion-item>
   
   
            <button
               ion-button
               color="primary"
               text-center
               block [disabled]="!form.valid">Log In</button>
         </ion-list> 
      </form>

   </div>


</ion-content>

The HTML should be fairly self-explanatory but there are a handful of points to pay attention to:

  • Use of the formGroup property binding to connect the form template with the Formbuilder object in the component's class
  • The form's ngSubmit event executes the logIn method of the component class
  • formControlName attributes on each field tie the value for that field to the corresponding key in the Formbuilder object
  • Submit button is disabled until the form fields have been completed

Additionally, the image used in the template:

<img 
   class="logo"
   src="assets/images/icon.png"
   alt="Ionic Framework logo">

Is stored within a custom directory named images (that you'll need to create within the sidemenu-demo/src/assets directory) and consists of the Ionic Framework logo (which you can simply download from Google Images and use in the project thereafter).

The LoginPage HTML template makes use of 2 CSS classes - login and logo - which define the styling for the page, both of which are declared within the component Sass file sidemenu-demo/src/pages/login/login.scss like so:

page-login {

   .logo {
	  display: block;
	  margin: 5em auto;
	  width: 150px;
   }


   .login {
      background: rgba(255, 255, 255, 1);
      border-radius: 10px; 
      margin: 1em;
      padding: 2em 5em 5em 2em;   
   
      p {
      	  margin-bottom: 3em; 
      }
   
   
      button {
      	  margin: 0 0 1.5em 0;
      }
   }

}

With the coding in place for the LoginPage component our project is now completed!

Let's go ahead and test this in the browser using the following command from the Ionic CLI:

ionic serve

Assuming no errors in coding were made you should see the log-in form for the application being displayed like so:

Ionic e-mail/password log-in form

Which, upon successful Firebase authentication, should redirect you to the HomePage component of the application where you can see the menu available for use like so:

Ionic sidemenu icon displayed on logged-in state of application

In summary

During this tutorial we've looked into using Firebase to authenticate logging in to an Ionic application built using a sidemenu template.

This has involved 'hiding' the sidemenu layout until the user has passed Firebase authentication - the functionality for which is supplied courtesy of a custom AuthProvider service.

Similar to what we developed in the previous tutorial this too could be enhanced and extended upon to include features such as the following:

  • Functionality to register an account which allows user's to join the service
  • Provide feedback for authentication errors (I.e. screen alerts or notifications informing the user of what has happened and what they might do to rectify this)
  • Add social sign-in features to the LoginPage component which would allow user's to sign-in using social media providers such as Facebook, Twitter or Google+

There are lots of ways in which this tutorial could be built on so feel free to explore and play!

If you enjoyed what you've read here then please sign up to my mailing list and, if you haven't done so already, take a look at my e-book: Mastering Ionic for information about working with forms and components in Ionic (doesn't cover Firebase).

Tags

Categories

Post a comment

All comments are welcome and the rules are simple - be nice and do NOT engage in trolling, spamming, abusiveness or illegal behaviour. If you fail to observe these rules you will be permanently banned from being able to comment.

Top