Implement a Search Bar on your Ionic app to filter your Firebase data

First Published: 16 November 2016
Updated on: 17 June 2019

Introduction

As you probably know by now, Firestore is a noSQL database, something a lot of people really like.

But if you come from a SQL background, like I do, sometimes things that are easy for other people end up being a real challenge for us.

I want to show you one way I found on how to integrate a search-bar with Firestore, so that you can start typing and your Ionic app filters all the data in real time.

By the end of this post, you’ll be able to integrate a search-bar with Firestore from your Ionic app, making data filtering easy on yourself without multiple database calls on keystroke.

Prefer the video version?

Let’s get coding

The first thing you’ll need is to create an configure your app, I usually write the creation and configuration process in every post, but it’s making it harder for me to keep them up-to-date.

So if you need to know how to create and initialize your Firebase app, just read this tutorial first.

The View

The first thing we’ll do is to create our view, it’s going to be something really simple, open the home.page.html file and create the search bar component and a list to loop through the items.

<ion-content>
  <ion-searchbar
    showcancelbutton=""
    (ioninput)="filterList($event)"
  ></ion-searchbar>
  <ion-list>
    <ion-item *ngfor="let item of goalList">
      <ion-label>{{ item.goalName }}</ion-label>
    </ion-item>
  </ion-list>
</ion-content>

The <ion-searchbar> is an Ionic component that gives us a really nice looking search bar at the top of our file.

And the list is looping through an array called goalList, showing our item’s name.

Now our job is to connect that search bar with the list, so every time the user types something the list gets updated.

The code

Go ahead and open home.page.ts and the first thing we’ll do is to import Firestore, after all, we need to get our data from somewhere :P

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

Now that Firebase is imported, we’re going to create a couple of variables just before the constructor:

public goalList: any[];
public loadedGoalList: any[];
  • goalList: Is to store the list of items we’re pulling from Firestore.
  • loadedGoalList: Is a bit of a hack, that I will explain when we get to it :)

Now, go and inject Firestore into the constructor:

constructor(private firestore: AngularFirestore) { }

After that, we’re going to pull our data from Firestore, but before, let me explain something.

There are 2 ways of using a search bar to query data:

  • Query the data real-time from the database, meaning it will send a query on every keystroke.
  • Pull the data and query it in your phone, meaning it pulls the data once and then filters that array.

We’re going to use the second method, because:

  • You can only query for the exact value in Firestore, so if you query for ‘Bench’ you won’t get ‘Bench Press’ as a result.
  • If you manage to pull that off, you’ll still be sending a hit to the database on every single keystroke.

Now that we got that out of the way, let’s pull the data from Firestore into the goalList array:

ngOnInit() {
  this.firestore.collection(`goals`).valueChanges()
    .subscribe(goalList => {
      this.goalList = goalList;
      this.loadedGoalList = goalList;
  });
}

We’re getting the data from our Firestore collection called goals, and assigning that data to both goalList and loadedGoalList.

By now you should be wondering WTF am I creating an extra array just to store the same data, so it’s time for a little story.

A few months ago, I spent an entire day debugging this same thing, I was trying to filter through a list of US colleges, the list had over 2K items.

Every time you type something in the search bar (as you’ll see next) it tries to initialize the countryList array and then filter it to see if the string you entered matches an object from the list.

That’s easy to understand, and it’s the behavior you’ll expect, but somehow, it just wasn’t working for me. Wanna guess why?

It turns out that my data was being returned as a promise, and the list was so big that by the time it was ready to be used the initialize function had already happened.

And it was trying to initialize an undefined array, so everything was breaking :P

So I came up with a little hack, and created 2 arrays, that way, when I need to initialize my data on keystroke, I just assign the value of the “backup” array, that happens on the spot, and my filter method then filters the right array.

So, now that you know why it’s there, let’s create the initialize function that we’ll use in our filter:

initializeItems(): void {
  this.goalList = this.loadedGoalList;
}

See? Every time we need to initialize our list, we’ll just use the ready-to-use loadedCountryList instead of calling the data again from Firebase.

And now we just need to create the filterList() function that is going to run on every keystroke on our search bar.

filterList(evt) {
  this.initializeItems();

  const searchTerm = evt.srcElement.value;

  if (!searchTerm) {
    return;
  }

  this.goalList = this.goalList.filter(currentGoal => {
    if (currentGoal.goalName && searchTerm) {
      if (currentGoal.goalName.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1) {
        return true;
      }
      return false;
    }
  });
}

Every time the function gets called, it:

  • Initializes the goalList array.
  • Sets searchTerm as what’s currently inside the search bar.
  • Checks if searchTerm is empty (if you were deleting) and returns nothing if it is.
  • It checks the string against the value of the goalName property of our list of goals.

Right now you should have a working search bar that’s filtering against a list on your Firestore database.