Firebase Authentication for Ionic Apps

First Published: 2 July 2018
Updated on: 24 July 2019

This is the second post of a 7 post series that will teach you how to build an app using Ionic Framework and Firebase from scratch.

  • Introduction and the Setup Process.
  • Authentication. => You are here
  • Handling Objects.
  • Handling Lists.
  • Firebase Transactions.
  • Firebase Cloud Storage.
  • Firebase Security Rules.

If you’ve ever built an authentication system you know it can be a pain, setting up secure servers, building the entire back-end, it can take a while when all you want is to focus on making your app great.

That right there is the main reason I chose Firebase as my backend.

In this lesson you’ll learn how to create an email and password authentication system, it will let your users do the three primary things every app needs to do:

  • Create a new account.
  • Login to an existing account.
  • Send a password reset email.

Initializing the Firebase App

The first thing we need to do for our app is to initialize it, that means telling Ionic how to connect to our Firebase database.

For that, we are going to be working on app.component.ts file.

The first thing you’re going to do in that file is to import Firebase, that way the file knows it needs to use it and has access to the methods we need.

We need to add the import line at the top of the file with the other imports:

import * as firebase from 'firebase/app';

That line of code right there imports the Firebase core functionality in the ‘firebase’ namespace, we’ll use it to initialize our app.

After that, we need to go into the constructor and call the initialize app function:

firebase.initializeApp();

That function takes a config object as a parameter, and the config object has all the API keys to connect to Firebase.

Now we need to go to our Firebase Console on the web, open a browser and navigate to https://console.firebase.google.com.

You’ll be asked to log in with your Google account, and then you’ll see a dashboard like this.

Firebase Dashboard

Click the create new app button, fill in the form and then you’ll be taken to your app’s dashboard.

To get that config object for your app, click the button that says “Add Firebase to your web app”.

Firebase Console

It will show you some initialization code, but focus just on this bit:

var config = {
  apiKey: '',
  authDomain: '',
  databaseURL: '',
  projectId: '',
  storageBucket: '',
  messagingSenderId: ''
};

In the Firebase Console you need to enable the authentication method for your app, go to Authentication > Sign-In Method > Email and Password

Auth Methods in the console

Also, let’s create a new database, so go into Database create a new Firestore database, and start it in test mode.

Going back to app.component.ts, you can either pass that object to the .initializeApp() function or you can create a different file to hold your credentials and then import them inside the component page.

Keeping the credentials in a separate file is a good practice if you plan to keep a public repo of your app, that way you can keep that file out of source control.

To do this, create a file called credentials.ts inside the src/app/ folder, the entire content of the file will be this:

// Initialize Firebase
export const firebaseConfig = {
  apiKey: '',
  authDomain: '',
  databaseURL: '',
  projectId: '',
  storageBucket: '',
  messagingSenderId: ''
};

Then, inside the app.component.ts file you can import the "firebaseConfig" object and pass it to the firebase initialization function:

import { firebaseConfig } from './credentials';

initializeApp() {
  firebase.initializeApp(firebaseConfig);
  ...
}

Preventing anonymous users from accessing pages

Now that Ionic knows how to talk to our Firebase application, we are going to create an authentication guard.

To explain the concept of an authentication guard, we need to first get a bit of context about how the navigation works on Ionic Framework.

Ionic uses Angular Router for navigation, which moves the user through the application using URLs, if you go ahead and open the src/app folder, you’ll find the file app-routing.module.ts, open it and you’ll see the CLI created a route for every page you generated.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: 'home', loadChildren: './home/home.module#HomePageModule' },
  {
    path: 'event-create',
    loadChildren:
      './pages/event-create/event-create.module#EventCreatePageModule'
  },
  ...
  ...
  ...
];
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

Those routes use lazy loading, meaning that when the app starts it’s not going to load them all, instead, it’s going to load them on a “need-this-now” case.

That’s why you don’t see the URLs pointing to a specific component, but instead to a module.

The URLs work like in a regular website, if you go to yourdomain.com/home it will take you to the home page.

Before we move to protecting the URLs we need to fix the detail URL first, go ahead and find the detail URL inside the file:

{
  path: 'event-detail',
  loadChildren:
    './pages/event-detail/event-detail.module#EventDetailPageModule',
}

To go to a detail page, we want to pass the ID of that object to the URL so that we can know which detail we need to fetch from the database, it should look like this:

{
  path: 'event-detail/:id',
  loadChildren:
    './pages/event-detail/event-detail.module#EventDetailPageModule',
}

Authentication Guard

Now that we have our URLs ready, we want to create an authentication guard, the job of this guard is to fetch for the user’s authentication state and return true/false depending on whether or not there’s a logged in user.

If it returns true the user will be able to navigate to that page, if it returns false we should redirect the user somewhere else.

Open your terminal and use the CLI to create the guard:

ionic generate guard services/user/auth

Since the guard is a service related to authentication we’re creating it in the /user folder. If you open it you’ll find something like this:

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

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  constructor() {}

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): boolean | Observable<boolean> | Promise<boolean> {
    return true;
  }
}

We’re going to use the onAuthStateChanged() method, it connects to Firebase Authentication and listens to changes in the user state to respond to them. You can read more about it in Firebase Official Documentation.

However, the TL;DR is that it adds an observer for auth state changes, meaning that whenever an authentication change happens, it will trigger the observer and the function inside it will run again.

import { Injectable } from '@angular/core';
import {
  CanActivate,
  ActivatedRouteSnapshot,
  RouterStateSnapshot,
  Router,
} from '@angular/router';
import { Observable } from 'rxjs';
import * as firebase from 'firebase/app';
import 'firebase/auth';

@Injectable({
  providedIn: 'root',
})
export class AuthGuard implements CanActivate {
  constructor(private router: Router) {}
  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): boolean | Observable<boolean> | Promise<boolean> {
    return new Promise((resolve, reject) => {
      firebase.auth().onAuthStateChanged((user: firebase.User) => {
        if (user) {
          resolve(true);
        } else {
          console.log('User is not logged in');
          this.router.navigate(['/login']);
          resolve(false);
        }
      });
    });
  }
}

We’re importing the core of Firebase functionality and then adding the auth functionality to the namespace:

import * as firebase from 'firebase/app';
import 'firebase/auth';

After that, we’re using the onAuthStateChanged() function to see if there’s a user, if there is, we resolve the promise with true, if there isn’t, we send back false and use the router to redirect the user to the login page.

Now that our Guard is created, we can call it from inside the app-routing.module.ts file:

import { AuthGuard } from './services/user/auth.guard';

{
  path: 'home',
  loadChildren: './pages/home/home.module#HomePageModule',
  canActivate: [AuthGuard],
},
{
  path: 'event-create',
  loadChildren:
    './pages/event-create/event-create.module#EventCreatePageModule',
  canActivate: [AuthGuard],
},
{
  path: 'event-detail/:id',
  loadChildren:
    './pages/event-detail/event-detail.module#EventDetailPageModule',
  canActivate: [AuthGuard],
}

All you need to do is add the canActivate property with the value of the AuthGuard (or any other guard you create) to the routes you want protected.

Now that our routing is complete, we can move to the next section and build the functionality we need in the authentication service.

Building the Authentication Service

We are going to create an authentication service, in the setup part of the app we created all the providers we need, for this one, we are going to use the AuthService to handle all the authentication related interactions between our app and Firebase.

Open the file auth.service.ts, it should look like this:

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

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  constructor() {}
}

We are going to start building on top of it, the first thing we will do is to import Firebase, we’re going to need the core functionality, auth, and the database added to the namespace:

import * as firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';

We need to create four functions inside this file. We need the user to be able to:

  • Login to an existing account.
  • Create a new account.
  • Send a password reset email.
  • Logout from the app.

The first function we will create is the LOGIN function, for that go ahead and create a loginUser() function that takes two string parameters (email, password):

loginUser(email:string, password:string): Promise<firebase.auth.UserCredential> {...}

And inside the function we’ll create the Firebase login:

loginUser(
  email: string,
  password: string
): Promise<firebase.auth.UserCredential> {
  return firebase.auth().signInWithEmailAndPassword(email, password);
}

We’re using the Firebase signInWithEmailAndPassword() method. The method takes an email and a password and logs in the user.

The way Firebase works, it does not have a regular “username” login, your users will need to use a valid email as a username.

If the function has an error, it will return the error code and message. For example, invalid email or password.

If the function goes through, the user will log in, Firebase will store the authentication object in localStorage, and the function will return a UserCredential object to a promise.

NOTE: If you are new to promises (like I was when I started working with Ionic) don’t worry after we are done building the authentication module I will do my best to explain what promises are and how they work.

The second function we need is a signup feature, but that is not all it has to do when a new user creates an account, we want to store the user’s email in our database.

SIDE-NOTE: Firestore and authentication are not “connected”, like that, creating a user does not store their information inside the database, it saves it in the authentication module of our app, so we need to copy that data inside the firestore database manually.

signupUser(email: string, password: string): Promise<any> {
  return firebase
    .auth()
    .createUserWithEmailAndPassword(email, password)
    .then((newUserCredential: firebase.auth.UserCredential) => {
      firebase
        .firestore()
        .doc(`/userProfile/${newUserCredential.user.uid}`)
        .set({ email });
    })
    .catch(error => {
      console.error(error);
      throw new Error(error);
    });
}

We are using the createUserWithEmailAndPassword() to create our new user (the name kinda says it all, right?)

This function is cool because after it creates the user, the app also logs the user in automatically (this wasn’t always true) meaning we do not have to call the login function again.

The function returns a Promise (we’ll talk about them later) that will run some code for us when it’s done creating the new user and login him into the app:

.then((newUserCredential: firebase.auth.UserCredential) => {
  firebase
    .firestore()
    .doc(`/userProfile/${newUserCredential.user.uid}`)
    .set({ email });
})

That is a reference to the userProfile collection inside our database.

We’re creating new collection called userProfile, and the UID identifies the user’s document.

Also, we’re adding a property called email, filling it with the new user’s email address.

User profile node inside Firebase database

SIDE-NOTE: Notice that I’m using template strings, inside .doc(), if you don’t know what those are, it’s a property of ES6 and TypeScript, notice that I’m not using double or single quotes, I’m using back ticks, in previous versions of JavaScript you’d need to do something like this to concatenate a string with a variable:

> const firstName = 'Jorge';
> const lastName = 'Vergara';
> const myName = 'My name is ' + firstName + ' ' + lastName;
> ```
>
> With ES6 or TypeScript you can use template strings, and it becomes:
>
> ```js
> const myName = `My name is ${firstName} ${lastName}`;
> ```
>
> So you see, the `${}` indicates that the thing inside needs to be evaluated, you can add any kind of JS inside, like: \${2 + 2} and it will output 4.
> Does that make it clearer?

We now need a function to let our users reset their passwords when they cannot remember them.

```js
resetPassword(email:string): Promise<void> {
  return firebase.auth().sendPasswordResetEmail(email);
}

We are using the sendPasswordResetEmail() it returns a void Promise, meaning that even though it does return a Promise, the promise is empty, so you mainly use it to perform other actions once it sends the password reset link.

And Firebase will take care of the reset login. They send an email to your user with a password reset link, the user follows it and changes his password without you breaking a sweat.

And lastly we’ll need to create a logout function:

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

That one does not take any arguments it checks for the current user and logs him out.

It also returns a void promise. You will mainly use it to move the user to a different page (probably to LoginPage).

And there we have all of our functions ready to use. Next, we will be creating the actual pages: login, signup, and password reset.

The Authentication Pages

By now we have a complete service or provider called AuthService that is going to handle all the Firebase <<>> Ionic authentication related communications, now we need to create the actual pages the user is going to see.

THE LOGIN PAGE

Open login.page.html and create a login form inside the ion-content tags to capture email and password:

<form [formGroup]="loginForm">
  <ion-item>
    <ion-label position="stacked">Email</ion-label>
    <ion-input
      formControlName="email"
      type="email"
      placeholder="Your email address"
      [class.invalid]="!loginForm.controls['email'].valid &&
      loginForm.controls['email'].touched"
    >
    </ion-input>
  </ion-item>
  <ion-item
    class="error-message"
    *ngIf="!loginForm.controls['email'].valid &&
       loginForm.controls['email'].touched"
  >
    <ion-label>Please enter a valid email address.</ion-label>
  </ion-item>

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

  <ion-button
    (click)="loginUser(loginForm)"
    expand="block"
    [disabled]="!loginForm.valid"
  >
    Log In
  </ion-button>
</form>

<ion-button expand="block" fill="clear" routerLink="/signup">
  Create a new account
</ion-button>

<ion-button expand="block" fill="clear" routerLink="/reset-password">
  I forgot my password :(
</ion-button>

That is an HTML form using Ionic components and Angular’s form module, there’s also form validation going on (and we will add more form validation stuff to the TypeScript file). We will not cover that in the book, but you can read about it in full detail in this post.

There are two things I want to explain here:

  • routerLink="/signup" is like using an <a href=""></a> tag with Angular router. It will evaluate the URL and navigate the user there.
  • You need to open the login.module.ts file and add ReactiveFormsModule to the imports array.

NOTE: You’ll also need to add ReactiveFormsModule to the import array of any module where you’re using reactive forms, for this app it’s mainly the authentication forms.

Now it is time to give it some style, and we are not going crazy with this, some margins and adding the .invalid class (which is a red border)

Open your login.page.scss and add the styles:

form {
  margin-bottom: 32px;
  button {
    margin-top: 20px !important;
  }
}

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

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

ion-input {
  padding: 5px;
}

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

.error-message {
  min-height: 2.2rem;
  ion-label {
    margin: 2px 0;
    font-size: 60%;
    color: #ff6153;
  }
  .item-inner {
    border-bottom: 0 !important;
  }
}

Like I said, nothing too fancy, a few margins to make everything look a bit better.

And finally it is time to jump into the actual TypeScript code, open your login.page.ts file, you should have something similar to this:

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-login',
  templateUrl: './login.page.html',
  styleUrls: ['./login.page.scss']
})
export class LoginPage implements OnInit {
  constructor() {}

  ngOnInit() {}
}

We will add the imports first, so everything is available when you need it:

import { Component, OnInit } from '@angular/core';
import { FormGroup, Validators, FormBuilder } from '@angular/forms';
import { LoadingController, AlertController } from '@ionic/angular';
import { AuthService } from '../../services/user/auth.service';
import { Router } from '@angular/router';

This is the breakdown of what we are importing:

  • LoadingController and AlertController because we are going to be using an alert pop up and a loading component inside our page.
  • FormGroup FormBuilder Validators are used to get form validation going on with angular.
  • AuthService is the authentication service we created, we will be using it to call the login function.
  • Router will handle navigation on this page, we want to send our user to the home page after logging in.

After everything is imported, we’re going to inject all the providers in the constructor, so they become available inside the class:

constructor(
  public loadingCtrl: LoadingController,
  public alertCtrl: AlertController,
  private authService: AuthService,
  private router: Router,
  private formBuilder: FormBuilder
) {...}

While we are at it, we are going to create two global variables for this class right before the constructor, one to handle our login form, and the other one to handle our loading component:

public loginForm: FormGroup;
public loading: HTMLIonLoadingElement;
constructor(...) {...}

Inside the constructor, we need to initialize our form:

constructor(
  public loadingCtrl: LoadingController,
  public alertCtrl: AlertController,
  private authService: AuthService,
  private router: Router,
  private formBuilder: FormBuilder
) {
  this.loginForm = this.formBuilder.group({
    email: ['',
      Validators.compose([Validators.required, Validators.email])],
    password: [
      '',
      Validators.compose([Validators.required, Validators.minLength(6)]),
    ],
  });
}

We are using formBuilder inside the constructor to initialize the fields and give them a required validator.

If you want to know more about FormBuilder check Angular’s docs.

Now let’s create our login function:

Our login function takes the values of the form fields and passes them to our loginUser function inside our AuthService service.

async loginUser(loginForm: FormGroup): Promise<void> {
  if (!loginForm.valid) {
    console.log('Form is not valid yet, current value:', loginForm.value);
  } else {
    this.loading = await this.loadingCtrl.create();
    await this.loading.present();

    const email = loginForm.value.email;
    const password = loginForm.value.password;

    this.authService.loginUser(email, password).then(
      () => {
        this.loading.dismiss().then(() => {
          this.router.navigateByUrl('home');
        });
      },
      error => {
        this.loading.dismiss().then(async () => {
          const alert = await this.alertCtrl.create({
            message: error.message,
            buttons: [{ text: 'Ok', role: 'cancel' }],
          });
          await alert.present();
        });
      }
    );
  }
}

It is also calling Ionic’s loading component since the app needs to communicate with the server to log the user in there might be a small delay in sending the user to the HomePage so we are using a loading component to give a visual so the user can understand that it is loading :P

The Password Reset Page

The first app (web app) I built didn’t have a password reset function. I was building it with PHP without any frameworks, hacking around stuff while learning on the go.

So every time someone needed to reset their password they needed to email me so I could manually reset it and then send them to their email (that was terrible!).

Firebase handles this for us. We create a page where the user inputs the email address, and we call the reset password function we set up in our authentication provider.

We are going to handle this the same way we did the login page (view, style, code).

Open your reset-password.page.html file, and create almost the same form we set up for the login page, with the email field (don’t forget to change the form name!)

<ion-content padding>
  <form [formGroup]="resetPasswordForm">
    <ion-item>
      <ion-label position="stacked">Email</ion-label>
      <ion-input
        formControlName="email"
        type="email"
        placeholder="Your email address"
        [class.invalid]="!resetPasswordForm.controls['email'].valid &&
        resetPasswordForm.controls['email'].touched"
      >
      </ion-input>
    </ion-item>
    <ion-item
      class="error-message"
      *ngIf="!resetPasswordForm.controls['email'].valid &&
      resetPasswordForm.controls['email'].touched"
    >
      <ion-label>Please enter a valid email.</ion-label>
    </ion-item>

    <ion-button
      expand="block"
      (click)="resetPassword(resetPasswordForm)"
      [disabled]="!resetPasswordForm.valid"
    >
      Reset your Password
    </ion-button>
  </form>
</ion-content>

REMEMBER to add the ReactiveFormsModule to the page module.

We’ll add basic margins and borders on our reset-password.page.scss file:

form {
  margin-bottom: 32px;
  button {
    margin-top: 20px !important;
  }
}

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

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

ion-input {
  padding: 5px;
}

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

.error-message {
  min-height: 2.2rem;
  ion-label {
    margin: 2px 0;
    font-size: 60%;
    color: #ff6153;
  }
  .item-inner {
    border-bottom: 0 !important;
  }
}

And now it’s time to code the functionality, open reset-password.page.ts, and like we did before, we are going to be adding the imports and injecting our services into the constructor:

import { Component, OnInit } from '@angular/core';
import { AuthService } from '../../services/user/auth.service';
import { AlertController } from '@ionic/angular';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';

@Component({
  selector: 'app-reset-password',
  templateUrl: './reset-password.page.html',
  styleUrls: ['./reset-password.page.scss'],
})
export class ResetPasswordPage implements OnInit {
  public resetPasswordForm: FormGroup;
  constructor(
    private authService: AuthService,
    private alertCtrl: AlertController,
    private formBuilder: FormBuilder,
    private router: Router
  ) {
    this.resetPasswordForm = this.formBuilder.group({
      email: [
        '',
        Validators.compose([Validators.required, Validators.email]),
      ],
    });
  }

  ngOnInit() {}

}

Then we create the password resetting function:

resetPassword(resetPasswordForm: FormGroup): void {
  if (!resetPasswordForm.valid) {
    console.log(
      'Form is not valid yet, current value:', resetPasswordForm.value
    );
  } else {
    const email: string = resetPasswordForm.value.email;
    this.authService.resetPassword(email).then(
      async () => {
        const alert = await this.alertCtrl.create({
          message: 'Check your email for a password reset link',
          buttons: [
            {
              text: 'Ok',
              role: 'cancel',
              handler: () => {
                this.router.navigateByUrl('login');
              },
            },
          ],
        });
        await alert.present();
      },
      async error => {
        const errorAlert = await this.alertCtrl.create({
          message: error.message,
          buttons: [{ text: 'Ok', role: 'cancel' }],
        });
        await errorAlert.present();
      }
    );
  }
}

Same as login, it takes the value of the form field, sends it to the AuthService and waits for Firebase’s response.

If there’s something about that file that you do not understand don’t hesitate to shoot me an email and I will be happy to help you.

The Signup Page

We are missing our signup page. This is the page that will be used by new users to create a new account, and I am going to be a bit more fast paced with this one (basically pasting the code) since it is the same as the login page but changing the forms name.

The first thing we will create is the view. This is how you want your signup.page.html to look like:

<ion-content padding>
  <form [formGroup]="signupForm">
    <ion-item>
      <ion-label position="stacked">Email</ion-label>
      <ion-input
        formControlName="email"
        type="email"
        placeholder="Your email address"
        [class.invalid]="!signupForm.controls['email'].valid && signupForm.controls['email'].touched"
      >
      </ion-input>
    </ion-item>
    <ion-item
      class="error-message"
      *ngIf="!signupForm.controls['email'].valid
      && signupForm.controls['email'].touched"
    >
      <ion-label>Please enter a valid email.</ion-label>
    </ion-item>

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

    <ion-button
      expand="block"
      (click)="signupUser(signupForm)"
      [disabled]="!signupForm.valid"
    >
      Create an Account
    </ion-button>
  </form>
</ion-content>

REMEMBER to add the ReactiveFormsModule to the page module.

Now add some margins on the signup.page.scss file:

form {
  margin-bottom: 32px;
  button {
    margin-top: 20px !important;
  }
}

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

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

ion-input {
  padding: 5px;
}

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

.error-message {
  min-height: 2.2rem;
  ion-label {
    margin: 2px 0;
    font-size: 60%;
    color: #ff6153;
  }
  .item-inner {
    border-bottom: 0 !important;
  }
}

And finally open your signup.page.ts file, import the services you’ll need and inject them to your constructor:

import { Component, OnInit } from '@angular/core';
import { AuthService } from '../../services/user/auth.service';
import { LoadingController, AlertController } from '@ionic/angular';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';

@Component({
  selector: 'app-signup',
  templateUrl: './signup.page.html',
  styleUrls: ['./signup.page.scss'],
})
export class SignupPage implements OnInit {
  public signupForm: FormGroup;
  public loading: any;
  constructor(
    private authService: AuthService,
    private loadingCtrl: LoadingController,
    private alertCtrl: AlertController,
    private formBuilder: FormBuilder,
    private router: Router
  ) {
    this.signupForm = this.formBuilder.group({
      email: [
        '',
        Validators.compose([Validators.required, Validators.email]),
      ],
      password: [
        '',
        Validators.compose([Validators.minLength(6), Validators.required]),
      ],
    });
  }

  ngOnInit() {}

}

You have done this twice now so there shouldn’t be anything new here, now create the signup function:

async signupUser(signupForm: FormGroup): Promise<void> {
  if (!signupForm.valid) {
    console.log(
      'Need to complete the form, current value: ', signupForm.value
    );
  } else {
    const email: string = signupForm.value.email;
    const password: string = signupForm.value.password;

    this.authService.signupUser(email, password).then(
      () => {
        this.loading.dismiss().then(() => {
          this.router.navigateByUrl('home');
        });
      },
      error => {
        this.loading.dismiss().then(async () => {
          const alert = await this.alertCtrl.create({
            message: error.message,
            buttons: [{ text: 'Ok', role: 'cancel' }],
          });
          await alert.present();
        });
      }
    );
    this.loading = await this.loadingCtrl.create();
    await this.loading.present();
  }
}

And there you have it. You have a fully functional auth system working on your app now.

If you run the app, you shouldn’t have any errors, and you can test the entire authentication flow.

A piece of advice, store what you have right now in a Github repository, you will be able to clone it every time you need to start an app that uses Firebase as an authentication backend, saving tons of time.

But Jorge, there’s no back button

You might have noticed that when you navigate from the login page to either the signup page or the reset password page there’s no back button to return to login.

That’s because Ionic 4 doesn’t include the back button by default, you have to drop it into the HTML in the pages you want it, so, if you want to add it to the signup page, you have to add it to the HTML like this:

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
      <ion-back-button defaultHref="/login"></ion-back-button>
    </ion-buttons>

    <ion-title>signup</ion-title>
  </ion-toolbar>
</ion-header>

Adding the component does the trick, Ionic handles all the logic in the background. Notice how we added the defaultHref="/login" that’s to make sure that even if the user reloads that page the back button shows up and redirects the user to the login screen.

Next Steps

This lesson was long, go for a walk, grab a cookie, or whatever you want to do for fun and then come back to the next one, we’re going to start talking about CRUD.

Next Lesson: Handling Objects.