Search Lessons, Code Snippets, and Videos
search by algolia
X
#native_cta# #native_desc# Sponsored by #native_company#

Autocomplete Search With Angular4 and Firebase

Episode 12 written by Jeff Delaney

In this lesson, we are going to create an autocomplete search feature that filters results asynchronously from the Firebase realtime database.

Database Structure

In order to demonstrate the search feature, the database has been populated with some data about movies. This is the structure of the NoSQL database.

-| movies
-| movieId
-| Title: string
-| Plot: string

Our search function will simply match string patterns for the movie title, similar to the SQL operator LIKE. It is not a very sophisticated search by any means, but it can get the job done for simple queries and basic autocomplete guessing. If you need a more complex full text search in Firebase, consider Elastic Search or something similar.

Generate the Resources

ng g service movies
ng g component movie-search

Build the Movies Service

This is a very simple service. We only need one function that returns a FirebaseListObservable with the queried movies.

The query will do a simple string match for the title of a movie. You can pass this function string values or RxJS subjects, as we will see in the component.

movies.service.ts

import { Injectable } from '@angular/core';
import { AngularFireDatabase, FirebaseListObservable } from 'angularfire2/database';

@Injectable()
export class MoviesService {

constructor(private db: AngularFireDatabase) { }

getMovies(start, end): FirebaseListObservable<any> {
return this.db.list('/movies', {
query: {
orderByChild: 'Title',
limitToFirst: 10,
startAt: start,
endAt: end
}
});
}

}

The Autocomplete Search Component

In the component, we start in the HTML by adding an input element. The component can listen for user search input by calling a function on every keydown event. This event will send the current value of the input, which will update the Firebase query with new starting and ending string patterns.

In order to show the results, we loop through the results with *ngFor. I also added a conditional template to show No results found… when the movies array is empty.

<h1>Movie Search</h1>
<input type="text" (keydown)="search($event)" placeholder="search movies..." class="input">

<div *ngFor="let movie of movies">
<h4>{{movie?.Title}}</h4>
<p>
{{movie?.Plot}}
</p>
</div>

<div *ngIf="movies?.length < 1">
<hr>
<p>
No results found :(
</p>
</div>

Now it’s time to implement the search functions. First, lets define a movies variable, which will hold the Firebase List Observable. During the OnInit lifecycle hook, we define the movies variable by subscribing to the observable.

Next, we implement the search function. It will simply take the value of the search form, then update the two subjects. The endAt subject gets an added PUA unicode character “\uf8ff”. This character comes after most other unicode characters, which allows Firebase to match the start and end of the string. This technique is not directly discussed in the Firebase docs and is based on this SO answer.

import { Component, OnInit } from '@angular/core';
import { MoviesService } from '../movies.service';
import { Subject } from 'rxjs/Subject'

@Component({
selector: 'movie-search',
templateUrl: './movie-search.component.html',
styleUrls: ['./movie-search.component.scss']
})
export class MovieSearchComponent implements OnInit {

movies;
startAt = new Subject()
endAt = new Subject()

constructor(private moviesSvc: MoviesService) { }

ngOnInit() {
this.moviesSvc.getMovies(this.startAt, this.endAt)
.subscribe(movies => this.movies = movies)
}

search($event) {
let q = $event.target.value
this.startAt.next(q)
this.endAt.next(q+"\uf8ff")
}

}

Demo of the autocomplete feature in action querying movies from Firebase. Demo of the autocomplete feature in action querying movies from Firebase.

Preventing Multiple Queries to Firebase

It’s very inefficient to hit the database after every keypress. It’s better to wait around 200ms after the last keypress so the query only runs when the user stops typing. Here’s how to implement a timeout in the Angular component.

We can implement this by keeping track of the last keypress time, then update the subjects only if the last keypress was more than 200ms ago.

lastKeypress: number = 0;

//...omitted

search($event) {
if ($event.timeStamp - this.lastKeypress > 200) {
let q = $event.target.value
this.startAt.next(q)
this.endAt.next(q+"\uf8ff")
}
this.lastKeypress = $event.timeStamp
}

Indexing the Title Field

As your database grows larger, it is also very inefficient to sort data without specifying an index - you should be seeing a Firebase warning in the console. You can achieve better performance by telling Firebase use an index with .indexOn and the title field like so:

Updated firebase database rules with .indexOn Updated firebase database rules with .indexOn

That’s it for Autocomplete with Firebase.