Learn How to Validate Forms with Ionic and Firebase (Client and Server Side)

First Published: 18 January 2017
Updated on: 24 September 2018

Are you sure the type of data you’re sending from your Ionic app is the one that’s storing in your Firebase database?

It turns out that for me, it wasn’t.

I was checking out a revenue tracking app I build for my wife, she organizes events, and I was convinced that all my programming was on point, little did I know that some of the ticket prices were storing in Firebase as string instead of number =/

That lead me to dig a little deeper into form validation with Ionic 2, and not only that but to look on how to validate the server side with Firebase, to make sure things were storing as I thought.

By the end of this post, you’ll learn how to validate your data with both Ionic Framework and Firebase.

Ionic and Firestore data validation

You’ll do this in 3 steps:

  • STEP #1: You’ll do client-side validation in the Ionic Form.
  • STEP #2: You’ll add an extra layer of safety with TypeScript’s type declaration.
  • STEP #3: You’ll validate the data server-side with Firebase

Make sure to get the code directly from Github so you can follow along with the post: https://github.com/javebratt/ionic-firebase-form-validation.

The first thing you’ll do is create your app and initialize Firebase, that’s out of the scope of this post (mainly because I’m tired of copy/pasting it).

If you don’t know how to do that you can read about it here first.

After your app is ready to roll, I want you to create a provider to handle the data (yeah, we’re using a provider even tho is only one function)

Open your terminal and create it like this:

ionic generate provider Firebase

The Ionic CLI will auto-magically import and initialize the provider for you, so everything is ready to start with the Form validation.

Step #1: Create and Validate the form in Ionic

We’re going to do something simple here. We’re creating a form that will take three inputs, a song’s name, its artist’s name, and the user’s age to make sure the user is over 18yo.

Go to home.ts and import angular’s form modules:

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

We’ll use FormBuilder to create the form, so go ahead and inject it into the controller.

public addSongForm:FormGroup;
constructor(public navCtrl: NavController, public formBuilder: FormBuilder,
    public firebaseData: FirebaseProvider) {}

Now we’re going to be initializing the form and declaring the inputs it’s going to have.

this.addSongForm = formBuilder.group({
  songName: [
    '',
    Validators.compose([Validators.required, Validators.maxLength(45)])
  ],
  artistName: [
    '',
    Validators.compose([Validators.required, Validators.minLength(2)])
  ],
  userAge: ['', Validators.compose([Validators.required])]
});

Let’s go through a bit of theory about what we just saw.

Angular forms module comes with a lot of pre-built goodies, one of those goodies is the Validators module, that module comes with pre-configured validators like required, minLength, and maxLength.

Without you doing any extra work, the Validators module is checking that the songName input won’t have more than 45 characters, or that the artist’s name needs at least 2 characters, or that all of the fields are required.

The cool thing tho is that we can kick it up a notch and create our validators

For example, I want to validate that users are over 18 years old, so I’m going to be requiring the user to fill the age field and validating that field is over 18.

I know there are probably 10 better ways to do this, but remember, that’s not the point :P

We’re going to create a validator that takes the age and makes sure it’s a number greater than or equal to 18.

For that I want you to create a folder called validators inside your src folder, and create a file called age.ts

Open up age.ts and let’s start creating our validator

The first thing you’ll do in that file is to import the module we’ll need:

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

Then create and export the class, I’m gonna call it AgeValidator:

export class AgeValidator {...}

And inside the class, we’ll create a method called isValid:

static isValid(control: FormControl): any {...}

Now inside that method we’ll verify the age:

if (control.value >= 18) {
  return null;
}
return { notOldEnough: true };

If the value it’s evaluating is greater than or equal to 18, it’s going to return null, but if it’s not, it will return that object.

Now that the validator is ready, go ahead and import it in home.ts:

import { AgeValidator } from '../../validators/age';

And add it to the userAge field initialization inside the constructor:

this.addSongForm = formBuilder.group({
  userAge: ['', Validators.compose([Validators.required, AgeValidator.isValid])]
});

The View Form

Now it’s time to go to home.html and start creating the form, first, delete everything inside the <ion-content></ion-content> tags.

And create a form there:

<form [formGroup]="addSongForm" (submit)="addSong()" novalidate></form>

The form is going to have a few things:

  • [formGroup]="addSongForm" is the name (and initialization in the ts file) we’re giving the form.
  • (submit)="addSong()" is telling Ionic that when this form is submitted it needs to run the addSong() function.
  • novalidate tells the browser to turn validation off, that way we handle the validation with the form modules.

After the form is created it’s time to add our first input. First we’ll create the input:

<ion-item>
  <ion-label stacked>Song Name</ion-label>
  <ion-input
    formControlName="songName"
    type="text"
    placeholder="What's the song's name?"
  >
  </ion-input>
</ion-item>

Then, we’ll show an error message if the form isn’t valid, so right after that input add a paragraph with the error message:

<ion-item
  class="error-message"
  *ngIf="!addSongForm.controls.songName.valid
  && addSongForm.controls.songName.touched"
>
  <p>
    The song's name is required to be under 45 characters.
  </p>
</ion-item>

We’re setting up the error message to hide, and only show if:

  • The form field isn’t valid.

AND

  • The form field is dirty (this just means the user already added value to it)

Let’s also add a CSS class to show a small red line if the field isn’t valid (you know, nothing says form errors like red lines)

<ion-input
  [class.invalid]="!addSongForm.controls.songName.valid
    &&  addSongForm.controls.songName.touched"
>
</ion-input>

That right there adds a CSS class called invalid if the form isn’t valid and has a value inside.

By the way, that’s one line of CSS

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

In the end, the entire input should look like this:

<ion-item>
  <ion-label stacked>Song Name</ion-label>
  <ion-input
    formControlName="songName"
    type="text"
    placeholder="What's the song's name?"
    [class.invalid]="!addSongForm.controls.songName.valid && addSongForm.controls.songName.touched"
  >
  </ion-input>
</ion-item>
<ion-item
  class="error-message"
  *ngIf="!addSongForm.controls.songName.valid && addSongForm.controls.songName.touched"
>
  <p>
    The song's name is required to be under 45 characters.
  </p>
</ion-item>

Now repeat this process 2 times to get the artist’s name:

<ion-item>
  <ion-label stacked>Artist Name</ion-label>
  <ion-input
    formControlName="artistName"
    type="text"
    placeholder="What's the artist's name?"
    [class.invalid]="!addSongForm.controls.artistName.valid && addSongForm.controls.artistName.touched"
  >
  </ion-input>
</ion-item>
<ion-item
  class="error-message"
  *ngIf="!addSongForm.controls.artistName.valid && addSongForm.controls.artistName.touched"
>
  <p>
    The artist's name has to be at least 2 characters long.
  </p>
</ion-item>

And to get the user’s age:

<ion-item>
  <ion-label stacked>How old are you?</ion-label>
  <ion-input
    formControlName="userAge"
    type="number"
    placeholder="I'm 30 years old."
    [class.invalid]="!addSongForm.controls.userAge.valid && addSongForm.controls.userAge.touched"
  >
  </ion-input>
</ion-item>
<ion-item
  class="error-message"
  *ngIf="!addSongForm.controls.userAge.valid && addSongForm.controls.userAge.touched"
>
  <p>
    You must be 18 or older to use this app.
  </p>
</ion-item>

And finally you’ll add a submit button:

<button ion-button block type="submit">
  Add Song
</button>

Let’s take it a step forward and disable the button until it’s valid:

<button ion-button block type="submit" [disabled]="!addSongForm.valid">
  Add Song
</button>

form validation

And there you have complete form validation working with an Ionic 2 app.

And for many apps, that’s it, that’s all the validation they offer, and that’s OK, kind of.

But we’re going to be taking things to a different level, and we’re going to work on having our form data validated in multiple ways to avoid weird surprises.

So we’ll add 2 extra layers

Step #2: Add TypeScript type declaration

For the type declarations, you’ll start working on your FirebaseData provider, to send the data to Firebase.

Go ahead and in the firebase-data.ts file import Firebase:

import firebase from 'firebase';

And then just create the function to push the new song to the database:

saveSong(songName, artistName, userAge) {
  return firebase.database().ref('songs')
    .push({ songName, artistName, userAge });
}

That’s a regular .push() function to add objects to a Firebase list, one cool thing I learned in ES6 for Everyone is that if the object properties and values have the same name you can just type them once, so:

.push({
  songName: songName,
  artistName: artistName,
  userAge: userAge
});

Becomes:

.push({ songName, artistName, userAge });

And now, add the type declarations, just as easy as:

saveSong(songName: string, artistName: string, userAge: number) {...}

That tells TypeScript that the song’s name has to be a string, the artist’s name has to be a string, and the user’s age needs to be a number.

Now in your home.ts file just create the addSong() function to send the data to the provider, it should be something like this:

addSong(){
  if (!this.addSongForm.valid){
    console.log("Nice try!");
  } else {
    this.firebaseData.saveSong(this.addSongForm.value.songName, this.addSongForm.value.artistName,
      parseFloat(this.addSongForm.value.userAge)).then( () => {
        this.addSongForm.reset();
      });
  }

}

If the form isn’t valid, don’t do anything, and if it is, then sends the data to the provider, I’m resetting all the input values after it’s saved.

See? We just added a small extra integrity layer for our data with little work.

And now it’s time to do the server side validation to make things EXTRA safe!

Step #3: Add server side data validation

For example, if somehow you managed to send “23” instead of 23 you’ll be sending a string instead of a number, and Firebase will just store it.

Then if you need to do some operations with that age, you’ll start getting errors.

Firebase provides a very detailed security language, where you can set who can see what.

For example, you can set your songs to be read only by the user who store it, or that only admins can save new songs, etc.

But what a lot of people don’t know, is that the Security Rules also let you write validation rules, where you can specify what kind of data you’re going to be storing (You can get ULTRA specific there)

To edit them, go to security rules:

You can find them in your Firebase console, right next to the database:

Firebase Security Rules Console

Then we’ll start adding the rules, to the simple read/write rules you have there, and you’ll add a validation rule to the songs node:

"songs": {
  "$songId": {
    ".validate": ""
  }
}

Inside that validate property we’ll be adding our rules.

Firebase has a few variables ready to use, like data, newData, and now.

We’ll be using newData since it’s the variable that refers to new data being save in our database.

If you want a list of all the variables and their explanation you can check Firebase official docs.

The first thing we need to make sure is that every new song saved has the three properties, the song’s name, the artist’s name and the user’s age.

For that we’ll use the .hasChildren() property:

"songs": {
  "$songId": {
    ".validate": "newData.hasChildren(['songName', 'artistName', 'userAge'])"
  }
}

In that line, we’re telling the Firebase database that every new song we save needs to have three children, one child called songName, another one called artistName and a third one called userAge.

Let’s start validating the song’s name first, remember that we set 2 rules for that field 1) it has to be a string and 2) it has to be less than 45 characters.

So let’s take care of the first one, making sure it’s a string, for that you’ll append another rule to the validate property:

newData.child("songName").isString()

That will make sure that when a new song is added, its property songName needs to be a string.

You can also append another rule and tell it to check its length and make sure it’s under 45 characters

newData.child("songName").val().length <= 45

Now let’s do the same with the artistName property:

newData.child("artistName").isString()

And

newData.child("songName").val().length > 1

And lastly we’ll need to validate the userAge field:

newData.child("userAge").isNumber() && newData.child("userAge").val() > 17

In the end, the security rules should look like this:

{
  "rules": {
    ".read": true,
    ".write": true,

    "songs": {
      "$songId": {
        ".validate": "newData.hasChildren(['songName', 'artistName', 'userAge']) && newData.child('songName').isString() && newData.child('songName').val().length <= 45 && newData.child('artistName').isString() && newData.child('songName').val().length > 1 && newData.child('userAge').isNumber() && newData.child('userAge').val() > 17"
      }
    }
  }
}

That will make sure that you’re always storing the right data if by any chance you send a string instead of a number for the user’s age, then the .push() method is going to throw a Permission Denied error.

And there you have it, you now have a complete validation flow, starting from validating your inputs in your app and moving all the way to do server-side validation with Firebase.

Go ahead and have a cookie, you deserve it, you just:

  • Used angular form modules to create validation in your form.
  • Used TypeScript to add validation layer.
  • and used Firebase security rules to do server-side validation.

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.