AngularFire Authentication for Ionic Framework: Step-by-Step Guide

First Published: 5 November 2016
Updated on: 24 September 2018

This post works with Ionic 3 and angularfire2, for a version about Ionic 4 and @angular/fire check this banner later, I’m working on it.

We’re going to create an AngularFire2 Authentication system for Ionic apps.

Angularfire2 makes certain things more natural, like syncing lists and managing the user’s authentication state in real-time.

Today we’re going to take advantage of that to create an Angularfire2 authentication process that covers:

  • Listening to authentication state in real-time.
  • The whole login, signup process.
  • A password reset functionality
  • …and more.

By the end of this post, your users will be able to create new accounts, log-in and reset their passwords when forgotten :)

We’re going to break everything into six main steps, so it’s easier to follow, in this post you’re going to be following:

  • Step #1: Creating the app.
  • Step #2: Create the authentication listener.
  • Step #3: Build the authentication provider.
  • Step #4: Build the Login page.
  • Step #5: Build the Password Reset page.
  • Step #6: Build the Signup page.

Now that you have a more general idea of what we’ll be doing let’s start with the first part.

Step #1: Creating the app

1.1) Make sure your development environment is up-to-date

Before writing any code, we are going to take a few minutes to install everything you need to be able to build this app, that way you won’t have to be switching context between coding and installing.

The first thing you’ll do is install node.js.

The second thing you’ll do is make sure you have Ionic and Cordova installed, you’ll do that by opening your terminal and typing:

npm install -g ionic cordova

Depending on your operating system (mostly if you run on Linux or Mac) you might have to add sudo before the npm install.... command.

1.2) Create your app

Now that you made sure everything is installed and up to date, you’ll create a new Ionic 2 app.

For this, you just need to (while still in your terminal) navigate to the folder you’d like to create it in.

For me, it’s my Development folder in my ~/ directory:

cd Development
ionic start af2EmailAuth blank
cd af2EmailAuth

What those lines there do is the following:

  • First, you’ll navigate to the Development folder.
  • Second, you’ll create the new Ionic 2 app:

  • ionic start creates the app.

  • af2EmailAuth is the name we gave it.

  • blank tells the Ionic CLI you want to start with the blank template.

  • Third, you’ll navigate into the new af2EmailAuth folder, that’s where all of your app’s code is going to be.

From now on, whenever you are going to type something in the terminal it’s going to be in this folder unless I say otherwise.

1.3) The npm packages that come with the project

When you use the Ionic CLI to create a new project, it’s going to do a lot of things for you, one of those things is making sure your project has the necessary npm packages/modules it needs.

That means, the start command is going to install ionic-angular all of the angularjs requirements and more, here’s what the package.json file would look like:

{
  "dependencies": {
    "@angular/common": "4.1.3",
    "@angular/compiler": "4.1.3",
    "@angular/compiler-cli": "4.1.3",
    "@angular/core": "4.1.3",
    "@angular/forms": "4.1.3",
    "@angular/http": "4.1.3",
    "@angular/platform-browser": "4.1.3",
    "@angular/platform-browser-dynamic": "4.1.3",
    "@ionic-native/core": "3.12.1",
    "@ionic-native/splash-screen": "3.12.1",
    "@ionic-native/status-bar": "3.12.1",
    "@ionic/storage": "2.0.1",
    "ionic-angular": "3.4.2",
    "ionicons": "3.0.0",
    "rxjs": "5.4.0",
    "sw-toolbox": "3.6.0",
    "zone.js": "0.8.12"
  }
}

Depending on when you read this, these packages might change (specially version numbers) so keep that in mind, also you can leave a comment below if you have any questions/issues/problems with this.

1.4) Install the packages you’ll need

Open your terminal (you should already be in the project folder) and install AF2:

npm install firebase  angularfire2 --save

1.5) Import and Initialize

Now you can initialize firebase by going to src/app/app.module.ts and importing everything you need from Firebase:

NOTE: Since you now need to declare every component, page, provider, etc. In this file, this would be a good moment to create them and add them all, just go ahead and run this commands in your terminal:

ionic generate page Login
ionic generate page ResetPassword
ionic generate page Signup

That will generate all the pages we need, and then:

ionic generate provider Auth

That will generate the provider we’ll use, don’t worry if you don’t understand something from those pages, we’ll explain every single one of those in detail when we get to them.

Now, you can open your app.module.ts and import everything we’ll be using, and this is the only time you’ll see this file :)

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, ErrorHandler } from '@angular/core';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { MyApp } from './app.component';

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

// Importing AF2 Module

import { AngularFireModule } from ' angularfire2';
import { AngularFireAuthModule } from ' angularfire2/auth';

// AF2 Settings
const firebaseConfig = {
  apiKey: 'AIzaSyALKfevapBOYK202f6k5mPPfMrT1MHDv5A',
  authDomain: 'bill-tracker-e5746.firebaseapp.com',
  databaseURL: 'https://bill-tracker-e5746.firebaseio.com',
  storageBucket: 'bill-tracker-e5746.appspot.com',
  messagingSenderId: '508248799540'
};

And then add the initialize to @NgModule:

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

In the end the file should look like this:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, ErrorHandler } from '@angular/core';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { MyApp } from './app.component';

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

// Importing AF2 Module

import { AngularFireModule } from ' angularfire2';
import { AngularFireAuthModule } from ' angularfire2/auth';

// AF2 Settings
const firebaseConfig = {
  apiKey: 'AIzaSyALKfevapBOYK202f6k5mPPfMrT1MHDv5A',
  authDomain: 'bill-tracker-e5746.firebaseapp.com',
  databaseURL: 'https://bill-tracker-e5746.firebaseio.com',
  storageBucket: 'bill-tracker-e5746.appspot.com',
  messagingSenderId: '508248799540'
};

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

We are using:

// AF2 Settings
export const firebaseConfig = {
  apiKey: '',
  authDomain: '',
  databaseURL: '',
  storageBucket: '',
  messagingSenderId: ''
};

To store our config object, and we’re importing only the principal and authentication module for angularfire2 since we’re not going to use the database in this example.

import { AngularFireModule } from ' angularfire2';
import { AngularFireAuthModule } from ' angularfire2/auth';

Then we’re using them to initialize our app:

imports: [
  BrowserModule,
  IonicModule.forRoot(MyApp),
  AngularFireModule.initializeApp(firebaseConfig),
  AngularFireAuthModule
],

Right there your app should be able to run without any errors when you do ionic serve.

You can find your firebaseConfig data in the Firebase’s Console.

You just go to the console, click on your app (or create a new one) and there it’s going to give you a few choices.

You’ll pick Add Firebase to your web app because remember we are going to use a mix of AF2 with the JS SDK.

Firebase Console

AngularFire2 Authentication with Ionic Framework

Step #2: Create an authentication observer

Since the new update, Firebase is already initialized in app.module.ts we’re just going to import the pages we’ll use and set up an authentication listener so AngularFire2 can listen to the user’s state and know if there’s a logged in user.

For example, if Mike was using the app and had an open session, when Mike wants to open the app again you don’t want to make him go through the login process again.

The auth listener will see that Mike had an open session and just send him to the HomePage instead of making him log-in or sign-up again.

For that just go to app/app.component.ts you should have something like this:

import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';

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

import { HomePage } from '../pages/home/home';

@Component({
  templateUrl: 'app.html'
})
export class MyApp {
  rootPage: any;

  constructor(platform: Platform, private splashScreen: SplashScreen,
    private statusBar: StatusBar) {

    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();
    });
  }
}

That’s the regular app.component.ts that you’ll find in a newly generated Ionic 2 project.

Then we’ll start adding the things we need.

First, we’ll need to import AF2, right below importing the HomePage go ahead and import:

import { AngularFireAuth } from ' angularfire2/auth';

And the last thing we need to do in app.component.ts is to make sure to subscribe to authentication to see if the user is authenticated.

If the user is authenticated, we’ll send them to the HomePage if they aren’t we’ll send them to the LoginPage where they can either sign-in with their credentials or just start using the app anonymously.

You’ll need to change the rootPage declaration from rootPage:any = HomePage; to rootPage:any;

That way we don’t send the user to the HomePage as soon as they open the app.

Then you’ll need to inject AF2 in the constructor:

constructor(platform: Platform, afAuth: AngularFireAuth, ...) {}

And then you’ll create the subscribe function:

const authObserver = afAuth.authState.subscribe(user => {
  if (user) {
    this.rootPage = HomePage;
    authObserver.unsubscribe();
  } else {
    this.rootPage = 'LoginPage';
    authObserver.unsubscribe();
  }
});

The subscribe function will listen for auth changes, if there’s a logged-in user the app will send her to the HomePage if not, she’ll get redirected to the LoginPage where she’ll be able to either log-in or start using the app anonymously.

So after that’s done, the app.component.ts file should look like this:

import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';

import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { HomePage } from '../pages/home/home';
import { AngularFireAuth } from ' angularfire2/auth';


@Component({
  templateUrl: 'app.html'
})
export class MyApp {
  rootPage:any;

  constructor(platform: Platform, afAuth: AngularFireAuth, private splashScreen: SplashScreen,
    private statusBar: StatusBar) {
    const authObserver = afAuth.authState.subscribe( user => {
      if (user) {
        this.rootPage = HomePage;
        authObserver.unsubscribe();
      } else {
        this.rootPage = 'LoginPage';
        authObserver.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();
    });
  }
}

Step #3: Create The Auth Provider

Now that you have everything initialized it’s time to create the auth provider, a provider is just like a module where you’ll handle the data part.

You technically could forget about it and add this functions in the Login, Sign-Up, Landing pages if you want to.

But a provider centralizes that information in one file, so if you need to use a function several times, you won’t be copy/pasting you’ll just call it from the provider.

If you decide to change something about your connection with Firebase you’ll just need to update that one file, instead of hunting down functions in other files.

We already created the provider when we were setting up the app, so open src/providers/auth/auth.ts and you’ll see something like this:

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

/*
Generated class for the AuthProvider provider.

See https://angular.io/docs/ts/latest/guide/dependency-injection.html
for more info on providers and Angular 2 DI.
*/
@Injectable()
export class AuthProvider {

  constructor(public http: Http) {
    console.log('Hello AuthProvider Provider');
  }

}

Notice how you didn’t have to write those lines of code :P

We aren’t going to use the Http functions, so you can remove them with the map operator as well since we aren’t connecting to a regular back-end but to a Firebase back-end which has its functions and SDK.

Now it looks like this:

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

@Injectable()
export class AuthProvider {
  constructor() {}
}

The first thing you need to do now is import everything you’ll need from AF2 to get this working:

import { AngularFireAuth } from ' angularfire2/auth';
import firebase from 'firebase/app';

We are importing AngularFireAuth to handle everything related to Firebase Authentication. Now inject AF2 in the constructor:

constructor(public afAuth: AngularFireAuth) {...}

By now your auth.ts file should look like this:

import { Injectable } from '@angular/core';
import { AngularFireAuth } from ' angularfire2/auth';
import firebase from 'firebase/app';

@Injectable()
export class AuthProvider {

  constructor(public afAuth: AngularFireAuth) {}
}

Now it’s time to create all the auth functions we’ll need. For this project you’ll need four auth functions:

  • A function to log-in a user with email and password.
  • A function to create a new user account.
  • A function to send a reset password link via email to your users.
  • A function to log-out from the app.

You’ll add this functions inside the AuthProvider class, right after your constructor.

3.1) Email and Password Log-In

We need a function for a returning user who already has her credentials, and she just wants to log-in using them:

loginUser(newEmail: string, newPassword: string): Promise<any> {
  return this.afAuth.auth.signInWithEmailAndPassword(newEmail, newPassword);
}

This function uses AF2, it takes two string parameters, an email, and a password and then logs in the user.

Hey, what if Mike forgot his password to use the app, he should have a function to have your app send him a reset password link, that’s on the easier side:

resetPassword(email: string): Promise<void> {
  return this.afAuth.auth.sendPasswordResetEmail(email);
}

3.3) Log the user out

And now we just need to have a log-out user.

logoutUser(): Promise<void> {
  return this.afAuth.auth.signOut();
}

3.4) Create a new user account

And lastly we’re going to allow our users to create an account on our app:

signupUser(newEmail: string, newPassword: string): Promise<any> {
  return this.afAuth.auth.createUserWithEmailAndPassword(newEmail, newPassword);
}

And that’s it. You have a fully complete provider that’s going to handle every use case for authentication. It should look like this:

import { Injectable } from '@angular/core';
import { AngularFireAuth } from ' angularfire2/auth';
import firebase from 'firebase/app';

@Injectable()
export class AuthProvider {

constructor(public afAuth: AngularFireAuth) {}

loginUser(newEmail: string, newPassword: string): Promise<any> {
  return this.afAuth.auth.signInWithEmailAndPassword(newEmail, newPassword);
}

resetPassword(email: string): Promise<any> {
  return this.afAuth.auth.sendPasswordResetEmail(email);
}

logoutUser(): Promise<any> {
  return this.afAuth.auth.signOut();
}

signupUser(newEmail: string, newPassword: string): Promise<any> {
  return this.afAuth.auth.createUserWithEmailAndPassword(newEmail, newPassword);
}

}

Now we’re going to create the actual pages.

Step #4: Create The Login Page

This is where you’ll users go when they already have an account and want to log-in to it.

login.html

Now move to login.html and create the template for it, it will have a two input form (email and password) and a button to log the user in.

I’m going to show you the template here, and it has a lot of form validation using the Angular2 formBuilder.

Explaining how to validate this is out of the scope of this post, but I do have another post on that topic here, it explains how to validate Ionic 2 forms using formBuilder.

<ion-header>
  <ion-navbar color="primary">
    <ion-title>
      Login
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <form [formGroup]="loginForm" (submit)="loginUser()" novalidate>
    <ion-item>
      <ion-label stacked>Email</ion-label>
      <ion-input
        #email
        formControlName="email"
        type="email"
        placeholder="Your email address"
        [class.invalid]="!loginForm.controls.email.valid &&
          loginForm.controls.email.dirty"
      ></ion-input>
    </ion-item>
    <ion-item
      class="error-message"
      *ngIf="!loginForm.controls.email.valid  &&
      loginForm.controls.email.dirty"
    >
      <p>Please enter a valid email.</p>
    </ion-item>

    <ion-item>
      <ion-label stacked>Password</ion-label>
      <ion-input
        #password
        formControlName="password"
        type="password"
        placeholder="Your password"
        [class.invalid]="!loginForm.controls.password.valid &&
          loginForm.controls.password.dirty"
      ></ion-input>
    </ion-item>
    <ion-item
      class="error-message"
      *ngIf="!loginForm.controls.password.valid  &&
      loginForm.controls.password.dirty"
    >
      <p>Your password needs more than 6 characters.</p>
    </ion-item>

    <button ion-button block type="submit">
      Login
    </button>
  </form>

  <button ion-button block clear (click)="goToResetPassword()">
    I forgot my password :-(
  </button>

  <button ion-button block clear (click)="createAccount()">
    Create a new account
  </button>
</ion-content>

A few things to note here:

  • As I said, we are using formGroup/formBuilder to handle all our form validation in Ionic 2.
  • We are adding validation for the email form to be an actual email.
  • We are validating that the password has more than six characters since that’s the default in Firebase.
  • We have a login button that runs this form.
  • A Reset your password button that sends the user to a page so they can reset their password if they forgot.
  • A link to send the user to the SignupPage.

login.scss

I’m not very good at CSS, well, actually I am, what I’m not good at is at design, thinking about how to make things pretty, so this is the CSS I’m using for the page, feel free to modify it to suit your needs:

page-login {
  form {
    margin-bottom: 32px;
    button {
      margin-top: 20px;
    }
  }

  p {
    font-size: 0.8em;
    color: #d2d2d2;
  }

  ion-label {
    margin-left: 5px;
  }

  ion-input {
    padding: 5px;
  }

  .invalid {
    border-bottom: 1px solid #ff6153;
  }

  .error-message .item-inner {
    border-bottom: 0 !important;
  }
}

login.ts

This TypeScript file is going to be a bit more challenging than the last one, but I’ll do my best to break it into bite-sized chunks.

We’ll be following the same approach as before. First, we’ll import everything we need:

You’d need both a Loading and Alert controller, the first one to create the loading component to show the user while Firebase does its thing, the second one to display to the user if there was an error while Firebase was doing its job.

import {
  IonicPage,
  NavController,
  LoadingController,
  Loading,
  AlertController
} from 'ionic-angular';

You’ll need a few things to validate your forms

import { FormBuilder, FormGroup, Validators } from '@angular/forms';

You’ll need the AuthProvider:

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

You’ll need the import the home page so you can push users there:

import { HomePage } from '../home/home';

And you’ll need the email validator to validate that users are entering actual email addresses on your form.

import { EmailValidator } from '../../validators/email';

NOTE: I created my email validator (actually I copy/pasted it from Stack Overflow) let’s take a minute here to build it, so it doesn’t cause any errors.

Create a folder called validators inside your src/ folder, and it should go like this:

  • app
  • assets
  • pages
  • providers
  • theme
  • validators

And inside it, create a file called email.ts and copy/paste this code:

import { FormControl } from '@angular/forms';

export class EmailValidator {
  static isValid(control: FormControl) {
    const re = /^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})\$/.test(
      control.value
    );

    if (re) {
      return null;
    }

    return {
      invalidEmail: true
    };
  }
}

That’s it, now back to the login page.

Now, right before the constructor let’s add the variables we’ll use for the form validation:

loginForm: FormGroup;
loading: Loading;

And let’s inject everything in our constructor:

constructor(public navCtrl: NavController, public authData: AuthProvider,
  public formBuilder: FormBuilder, public alertCtrl: AlertController,
  public loadingCtrl: LoadingController) {...}

So far nothing that we haven’t seen before, now we are going to create an instance of our login form so that we can add the validation rules to it.

this.loginForm = formBuilder.group({
  email: [
    '',
    Validators.compose([Validators.required, EmailValidator.isValid])
  ],
  password: [
    '',
    Validators.compose([Validators.minLength(6), Validators.required])
  ]
});

We are using:

  • A required validator in both fields to indicate that they are both mandatory.
  • An EmailValidator in the email field that we created a few minutes ago, this to make sure it’s an actual email.
  • A min-length validator to make sure the password is at least six characters long, mainly because Firebase requires passwords to be at least six characters long and if we can do this on the front-end it’s one less error to take care of.

Now let’s get out of the easy functions first:

goToResetPassword(){
  this.navCtrl.push('ResetPasswordPage');
}

createAccount(){
  this.navCtrl.push('SignupPage');
}

You’ve seen that before, that will let your user move to the ResetPasswordPage if they forget their password and can’t log in or to the SignupPage to create a new account.

Now, the login function is built into several parts, but basically, it:

  • Checks if the form is valid.
  • Sends data to the authentication provider.
  • It triggers a loading spinner.
  • On successful account creation, it sends the user to the homepage.
  • On error is shows an alert.
loginUser(){
  if (!this.loginForm.valid){
    console.log(this.loginForm.value);
  } else {
    this.authData.loginUser(this.loginForm.value.email, this.loginForm.value.password)
    .then( authData => {
      this.navCtrl.setRoot('HomePage');
    }, error => {
      this.loading.dismiss().then( () => {
        let alert = this.alertCtrl.create({
          message: error.message,
          buttons: [
            {
              text: "Ok",
              role: 'cancel'
            }
          ]
        });
        alert.present();
      });
    });

    this.loading = this.loadingCtrl.create({
      dismissOnPageChange: true,
    });
    this.loading.present();

}
}

Your complete login.ts file should look like this:

import { Component } from '@angular/core';
import {
  IonicPage,
  NavController,
  LoadingController,
  Loading,
  AlertController } from 'ionic-angular';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AuthProvider } from '../../providers/auth/auth';
import { EmailValidator } from '../../validators/email';

@IonicPage()
@Component({
  selector: 'page-login',
  templateUrl: 'login.html'
})
export class LoginPage {

  public loginForm:FormGroup;
  public loading:Loading;

  constructor(public navCtrl: NavController, public authData: AuthProvider,
    public formBuilder: FormBuilder, public alertCtrl: AlertController,
    public loadingCtrl: LoadingController) {

      this.loginForm = formBuilder.group({
        email: ['', Validators.compose([Validators.required, EmailValidator.isValid])],
        password: ['', Validators.compose([Validators.minLength(6), Validators.required])]
      });
  }

  loginUser(){
    if (!this.loginForm.valid){
      console.log(this.loginForm.value);
    } else {
      this.authData.loginUser(this.loginForm.value.email, this.loginForm.value.password)
      .then( authData => {
        this.navCtrl.setRoot('HomePage');
      }, error => {
        this.loading.dismiss().then( () => {
          let alert = this.alertCtrl.create({
            message: error.message,
            buttons: [
              {
                text: "Ok",
                role: 'cancel'
              }
            ]
          });
          alert.present();
        });
      });

      this.loading = this.loadingCtrl.create({
        dismissOnPageChange: true,
      });
      this.loading.present();
    }
  }

  goToResetPassword(){
    this.navCtrl.push('ResetPasswordPage');
  }

  createAccount(){
    this.navCtrl.push('SignupPage');
  }

}

Step #5: The Reset Password Page

This is where you’ll users go when they forget their passwords and need to reset them.

reset-password.html

This is going to be just like the login.html just easier since it’s only one field, our email field.

<ion-header>
  <ion-navbar color="primary">
    <ion-title>
      Reset your Password
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <form [formGroup]="resetPasswordForm" (submit)="resetPassword()" novalidate>
    <ion-item>
      <ion-label stacked>Email</ion-label>
      <ion-input
        formControlName="email"
        type="email"
        placeholder="Your email address"
        [class.invalid]="!resetPasswordForm.controls.email.valid && resetPasswordForm.controls.email.dirty"
      ></ion-input>
    </ion-item>
    <ion-item
      class="error-message"
      *ngIf="!resetPasswordForm.controls.email.valid  && resetPasswordForm.controls.email.dirty"
    >
      <p>Please enter a valid email.</p>
    </ion-item>

    <button ion-button block type="submit">
      Reset your Password
    </button>
  </form>
</ion-content>

reset-password.scss

The CSS is the same, a few margins and a color for errors:

page-reset-password {
  form {
    margin-bottom: 32px;
    button {
      margin-top: 20px;
    }
  }

  p {
    font-size: 0.8em;
    color: #d2d2d2;
  }

  ion-label {
    margin-left: 5px;
  }

  ion-input {
    padding: 5px;
  }

  .invalid {
    border-bottom: 1px solid #ff6153;
  }

  .error-message .item-inner {
    border-bottom: 0 !important;
  }
}

reset-password.ts

This file is going to be a bit fast paced since we’ve already done this in the login.ts file.

First get all the imports ready:

import { Component } from '@angular/core';
import { IonicPage, NavController, AlertController } from 'ionic-angular';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AuthProvider } from '../../providers/auth/auth';
import { EmailValidator } from '../../validators/email';

Add the form validation variables:

public resetPasswordForm:FormGroup;

Inject everything to the constructor and initialize the form:

constructor(public authData: AuthProvider, public formBuilder: FormBuilder,
  public nav: NavController, public alertCtrl: AlertController) {

  this.resetPasswordForm = formBuilder.group({
    email: ['', Validators.compose([Validators.required, EmailValidator.isValid])],
  })
}

And finally the actual resetPassword() function, it will check if the form is valid and only call the auth provider if it is:

resetPassword(){
    if (!this.resetPasswordForm.valid){
      console.log(this.resetPasswordForm.value);
    } else {
      this.authData.resetPassword(this.resetPasswordForm.value.email)
      .then((user) => {
        let alert = this.alertCtrl.create({
          message: "We just sent you a reset link to your email",
          buttons: [
            {
              text: "Ok",
              role: 'cancel',
              handler: () => {
                this.nav.pop();
              }
            }
          ]
        });
        alert.present();
      }, (error) => {
        var errorMessage: string = error.message;
        let errorAlert = this.alertCtrl.create({
          message: errorMessage,
          buttons: [
            {
              text: "Ok",
              role: 'cancel'
            }
          ]
        });
        errorAlert.present();
      });
    }
  }

Nothing too weird there, since we just went through the same thing in the login portion of the tutorial.

Step #6: The Sign-up Page

We do need a sign-up page, so here we go:

signup.html

As you can imagine, there’s no difference between the signup and the login template other than their names and the names of the forms, so you should be able to handle this one on your own.

Just kidding, here’s the code:

<ion-header>
  <ion-navbar color="primary">
    <ion-title>
      Create an Account
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <form [formGroup]="signupForm" (submit)="signupUser()" novalidate>
    <ion-item>
      <ion-label stacked>Email</ion-label>
      <ion-input
        formControlName="email"
        type="email"
        placeholder="Your email address"
        [class.invalid]="!signupForm.controls.email.valid && signupForm.controls.email.dirty"
      ></ion-input>
    </ion-item>
    <ion-item
      class="error-message"
      *ngIf="!signupForm.controls.email.valid  && signupForm.controls.email.dirty"
    >
      <p>Please enter a valid email.</p>
    </ion-item>

    <ion-item>
      <ion-label stacked>Password</ion-label>
      <ion-input
        formControlName="password"
        type="password"
        placeholder="Your password"
        [class.invalid]="!signupForm.controls.password.valid && signupForm.controls.password.dirty"
      ></ion-input>
    </ion-item>
    <ion-item
      class="error-message"
      *ngIf="!signupForm.controls.password.valid  && signupForm.controls.password.dirty"
    >
      <p>Your password needs more than 6 characters.</p>
    </ion-item>

    <button ion-button block type="submit">
      Create an Account
    </button>
  </form>
</ion-content>

Does it need explaining? If it does leave a comment below, don’t think I need to add another block here for this one.

signup.scss

This is just a few margins and colors, and I’m sure you can do a better job than me here:

page-signup {
  form {
    margin-bottom: 32px;
    button {
      margin-top: 20px;
    }
  }

  p {
    font-size: 0.8em;
    color: #d2d2d2;
  }

  ion-label {
    margin-left: 5px;
  }

  ion-input {
    padding: 5px;
  }

  .invalid {
    border-bottom: 1px solid #ff6153;
  }

  .error-message .item-inner {
    border-bottom: 0 !important;
  }
}

signup.ts

This is the same thing that we’ve already done twice:

import { Component } from '@angular/core';
import {
  IonicPage,
  NavController,
  LoadingController,
  Loading,
  AlertController } from 'ionic-angular';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AuthProvider } from '../../providers/auth/auth';
import { HomePage } from '../home/home';
import { EmailValidator } from '../../validators/email';

@IonicPage()
@Component({
  selector: 'page-signup',
  templateUrl: 'signup.html',
})
export class SignupPage {
  public signupForm: FormGroup;
  public loading: Loading;

  constructor(public nav: NavController, public authData: AuthProvider,
    public formBuilder: FormBuilder, public loadingCtrl: LoadingController,
    public alertCtrl: AlertController) {

    this.signupForm = formBuilder.group({
      email: ['', Validators.compose([Validators.required, EmailValidator.isValid])],
      password: ['', Validators.compose([Validators.minLength(6), Validators.required])]
    });
  }

  /**
   * If the form is valid it will call the AuthData service to sign the user up password displaying a loading
   *  component while the user waits.
   *
   * If the form is invalid it will just log the form value, feel free to handle that as you like.
   */
  signupUser(){
    if (!this.signupForm.valid){
      console.log(this.signupForm.value);
    } else {
      this.authData.signupUser(this.signupForm.value.email, this.signupForm.value.password)
      .then(() => {
        this.nav.setRoot(HomePage);
      }, (error) => {
        this.loading.dismiss().then( () => {
          var errorMessage: string = error.message;
            let alert = this.alertCtrl.create({
              message: errorMessage,
              buttons: [
                {
                  text: "Ok",
                  role: 'cancel'
                }
              ]
            });
          alert.present();
        });
      });

      this.loading = this.loadingCtrl.create({
        dismissOnPageChange: true,
      });
      this.loading.present();
    }
  }
}

What you should have now.

By now you should have 2 things.

You should have a working auth process, that will take your user to a login page, give access to the home page, let them login or reset their password if they forgot.

But most importantly, you should have a better understanding of how authentication works on Firebase/AF2.

When there’s an authentication change, Firebase will handle that automatically, saving the user’s state in localStorage, so you don’t have to think about it.

That’s by far one of my favorite things about Firebase, I used to be a back-end python developer, so I’m familiar with the amount of work it takes to build your authentication API, so it’s refreshing that they just provide everything working out of the box.

Next Steps

If you have any questions don’t forget you can always leave a comment below.

If you want to take a deeper dive on Ionic + Firestore you should go through my FREE course: Build your first Firestore powered Ionic app.