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

Tools used:
Backend: Firebase -- Version: 7.14.4
Frontend: @ionic/angular -- Version: 5
Helper Library: @angular/fire -- Version: 6.0.0

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, for that you can refer to this tutorial first.

The View

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

<ion-content [fullscreen]="true">
  <ion-searchbar
    showcancelbutton=""
    (ionInput)="filterList($event)"
  ></ion-searchbar>

  <ion-list lines="none">
    <ion-item *ngFor="let foodItem of foodList">
      <ion-label class="ion-text-wrap">
        <ion-text color="primary">
          <h3>{{ foodItem.name }}</h3>
        </ion-text>
        <p>{{ foodItem.quantity }} -- {{ foodItem.type }}</p>
      </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 foodList, showing our item’s name, quantity, and type.

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';
import { first } from 'rxjs/operators';

Now that Firebase is imported, let’s create the foodList variable as a public class variable before the constructor:

public foodList: any[];

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 foodList array:

async ngOnInit() {
  this.foodList = await this.initializeItems();
}

async initializeItems(): Promise<any> {
  const foodList = await this.firestore.collection('foodList')
    .valueChanges().pipe(first()).toPromise();
  return foodList;
}

We’re getting the data from our Firestore collection called foodList, and assigning that data to this.foodList.

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

async filterList(evt) {
  this.foodList = await this.initializeItems();
  const searchTerm = evt.srcElement.value;

  if (!searchTerm) {
    return;
  }

  this.foodList = this.foodList.filter(currentFood => {
    if (currentFood.name && searchTerm) {
      return (currentFood.name.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1);
    }
  });
}

Every time the function gets called, it:

  • Initializes the foodList 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 name property of our list of foods.

By the way, filter() is an array function, if like me, you jumped into Ionic development without a JS background, I recommend you take either Beginner JavaScript or ES6 for Everyone from Wes Bos, the ES6 course helped me learn enough JS that I started realizing how many things JS already does I was trying to re-invent, allowing me to remove a lot of code I didn’t need in the first place.

And that’s it’, every time you type something it’s going to try and match it against the food’s name property.

But, what if you want to do a wider search, and also be able to type “lunch” or “snack” and get all the foods marked as lunch or snack?

Well, for that we need to add the food’s type property (or any property you want to match against) to the conditional, for example:

async filterList(evt) {
  this.foodList = await this.initializeItems();
  const searchTerm = evt.srcElement.value;

  if (!searchTerm) {
    return;
  }

  this.foodList = this.foodList.filter(currentFood => {
    if (currentFood.name && searchTerm) {
      return (currentFood.name.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1 || currentFood.type.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1);
    }
  });
}

And that’s it, please let me know if this worked or if you had any issues, I’ll do my best to help you debug them :-)