Using Cloud Firestore in Angular With AngularFire

Cloud Firestore was just announced as a new database to fill-in the gap where the Firebase realtime database may not be the best tool. Cloud Firestore is a NoSQL, document-based database. At the root of the database are collections (e.g.: todos, users, files) and collections can only contain documents. Documents contain key-value pairs and can contain collections of their own, called subcollections. This therefore makes it easier to build apps that have more complex hierarchical needs compared with the flat JSON tree offered with the traditional Firebase realtime database.

Here’s we’ll cover the very basics of using interacting with Cloud Firestore in an Angular 2+ project. You’ll need to have a Firebase account and to enable the new database.

Setup

First, install the needed Firebase packages (firebase & angularfire2) into your Angular project:

$ yarn add firebase angularfire2

# or, using npm:
$ npm install firebase angularfire2

And then add both the AngularFireModule and AngularFirestoreModule to your app module:

app.module.tsc

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AngularFireModule } from 'angularfire2';
import { AngularFirestoreModule } from 'angularfire2/firestore';
import { environment } from '../environments/environment';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    AngularFireModule.initializeApp(environment.firebase),
    AngularFirestoreModule.enablePersistence()
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

Note that we call the enablePersistence method on the AngularFirestoreModule to automatically enable local caching on Chrome, Safari and Firefox, which will allow the app to stay available while offline.

And with this, your Firebase app configuration would be inside the enviroment.ts file like this:

/enviroments/enviroment.ts

export const environment = {
  production: false,
  firebase: {
    apiKey: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
    authDomain: 'your-project-id.firebaseapp.com',
    databaseURL: 'https://your-project-id.firebaseio.com',
    projectId: 'your-project-id',
    storageBucket: 'your-project-id.appspot.com',
    messagingSenderId: 'XXXXXXXXXXXX'
  }
};

Basic Usage

Now that our app is configured with Firebase, we can start playing around with the database. We’ll demonstrate a few of the CRUD operations around the idea of a simple todo app that contains a todos collection and documents within that collection that contain a description and a completed field.

First you’ll want to inject the AngularFirestore injectable into your component:

import { Component } from '@angular/core';
import { AngularFirestore } from 'angularfire2/firestore';

@Component({ ... })
export class AppComponent {
  constructor(private afs: AngularFirestore) {
    // ...
  }
}

To get access to a collection, you create a reference to it with something like this:

this.todoCollectionRef = this.afs.collection('todos'); // a ref to the todos collection

Creating a reference to a collection doesn't do any network call and it's safe to reference collections that don't exist, as the collection will be automatically created if needed. A collection without any document will also be automatically removed.

You can then listen for changes on the collection’s documents by calling valueChanges() on the collection reference:

this.todo$ = this.todoCollectionRef.valueChanges();

The valueChanges method returns an observable of the documents in the collection. There’s also a snapshotChanges, method that also returns the id as well as metadata about our documents. Read-on for an example using snapshotChanges instead.


Here’s therefore our starting todo app, which will get the documents inside the todos collection from the database:

app.component.ts

import { Component } from '@angular/core';
import {
  AngularFirestore,
  AngularFirestoreCollection
} from 'angularfire2/firestore';

import { Observable } from 'rxjs/Observable';

export interface Todo {
  description: string;
  completed: boolean;
}

@Component({ ... })
export class AppComponent {
  todoCollectionRef: AngularFirestoreCollection<Todo>;
  todo$: Observable<Todo[]>;

  constructor(private afs: AngularFirestore) {
    this.todoCollectionRef = this.afs.collection<Todo>('todos');
    this.todo$ = this.todoCollectionRef.valueChanges();
  }
}

And we can display our todo items in the template with something as simple as this:

app.component.html

<ul>
  <li *ngFor="let todo of todo$ | async"
      [class.completed]="todo.completed">
    {{ todo.description }}
  </li>
</ul>

Adding items

To add a new document in a collection, simply call add on the collection reference:

addTodo(todoDesc: string) {
  if (todoDesc && todoDesc.trim().length) {
    this.todoCollectionRef.add({ description: todoDesc, completed: false });
  }
}

Thanks to our todo collection reference being typed against our Todo interface, the TypeScript compiler will know the shape of the data that should be passed using the add method.

Updating and Deleting Items

To update or delete a document in the collection, we’ll also need the document’s id, which is not returned with the valueChanges method. We’ll change our implementation a bit to use the snapshotChanges method instead and also include the id for each todo document:

app.component.ts

import { Component } from '@angular/core';
import {
  AngularFirestore,
  AngularFirestoreCollection
} from 'angularfire2/firestore';

import { Observable } from 'rxjs/Observable';

export interface Todo {
  id?: string;
  description: string;
  completed: boolean;
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  todoCollectionRef: AngularFirestoreCollection<Todo>;
  todo$: Observable<Todo[]>;

  constructor(private afs: AngularFirestore) {
    this.todoCollectionRef = this.afs.collection<Todo>('todos');
    this.todo$ = this.todoCollectionRef.snapshotChanges().map(actions => {
      return actions.map(action => {
        const data = action.payload.doc.data() as Todo;
        const id = action.payload.doc.id;
        return { id, ...data };
      });
    });
  }
}

We map over the observable returned by snapshotChanges to extract the document’s id along with its data.

Actions returned by snapshotChanges are of type DocumentChangeAction and contain a type and a payload. The type is either added, modified or removed and the payload contains the document's id, metadata and data.

With this in place, everything still works the same, and we can now easily implement updating and deleting todo documents and have the changes reflected immediately to our template:

app.component.ts (partial)

updateTodo(todo: Todo) {
  this.todoCollectionRef.doc(todo.id).update({ completed: !todo.completed });
}

deleteTodo(todo: Todo) {
  this.todoCollectionRef.doc(todo.id).delete();
}

Learning more

  Tweet It
✖ Clear

🕵 Search Results

🔎 Searching...