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, 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.

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

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 service to handle the data.

Open your terminal and create it like this:

ionic generate service services/firebase

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.

addSongForm:FormGroup;
constructor(
  private readonly formBuilder: FormBuilder,
  private readonly 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 position="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 position="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 position="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 position="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:

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

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

<ion-button expand="block" type="submit" [disabled]="!addSongForm.valid"> Add Song </ion-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 service, to send the data to Firebase.

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

import { collection, Firestore, addDoc } from '@angular/fire/firestore';

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

constructor(private readonly firestore: Firestore) {}

saveSong(songName, artistName, userAge) {
  return addDoc(collection(this.firestore, `songs`), { songName, artistName, userAge })
}

That’s a regular addDoc() 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:

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

Becomes:

.add({ 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

You can do some validation in your security rules, things like making sure exact properties are there, or that they’re equal to something or something else.

For example:

Let’s say you want to make sure that the user’s age is a number and it’s over 18, then you can write a security rule like this:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    match /users/{userId}/{document=**} {
      allow read: if true;
      allow write: if request.resource.data.age is number && request.resource.data.age > 18
    }
  }
}

You can learn a lot more going through the Firebase official docs or looking at the API reference 😃.

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 addDoc() 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.