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.
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:
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';
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:
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'
}
};
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:
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[]>;
And we can display our todo items in the template with something as simple as this:
<ul>
<li *ngFor="let todo of todo$ | async"
[class.completed]="todo.completed">
{{ todo.description }}
</li>
</ul>
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.
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:
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[]>;
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:
updateTodo(todo: Todo) {
this.todoCollectionRef.doc(todo.id).update({ completed: !todo.completed });
}
deleteTodo(todo: Todo) {
this.todoCollectionRef.doc(todo.id).delete();
}
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Could you please continue the implementation with the
todo$
Observable in the CRUD? I can’t see a Read and Query methods implemented here. :)You rock!! This post was helpful! Just one thing you might need to update, now use use
@angular/fire
instead of the deprecated ones. ;)