Getting started with AngularFire and Firebase

In this post we'll look at setting up a Firebase project and use AngularFire to interact with our Firestore database.  We'll cover basic Create, Read, Update and Delete (CRUD) operations.  

The repository below contains the demo application used to perform these operations, I recommend cloning/forking it in order to follow along https://github.com/tomeustace/angularfire-basics

Firebase Project Setup

Install Firebase npm install firebase

Create new project firebase init

So now we need to create our realtime database in firebase.

In the next screen you'll be asked to select a location, so choose whatever makes sense for you.  Then you'll be directed to your newly minted database.

Next you will need to create an app to connect to you Firebase project.  So go to the Project Settings and select Web icon for our app.  This will walk you through some options for creating your app.  Hosting is free so I'd recommend using it.

You'll be asked to install the firebase SDK and it will show you your custom firebase config.  This config will be added to our Angular project when we do the AngularFire install below, so we can ignore it for now.

Then you'll install the firebase-tools npm install -g firebase-tools

I'll skip the actual deploy.

Angular App

Install Angular if not installed already npm install -g @angular/cli

Create a new Angular project ng new my-project

Add AngularFire using schematic ng add @angular/fire

The angular fire install will ask you a few questions about your setup.  It will recognize and configure the firebase web app we created above.  The main config we are interested in is Firestore, I also selected the hosting option here.

If you now look at your git diff you'll see the changes the install has made to your Angular application.

Now you can run ng serve and visit http://localhost:4200.  If everything went to plan you should see the demo app running.  

Interacting with Firestore using AngularFire

Below shows the demo app running next to to the Firestore database.  I recommend running the app and your Firebase console view side by side as shown below, so you can see the results of operations.  Of course, you could modify to show them in app.

All Operations Are Contained in the CollectionService, shown at bottom of this post.  We'll take a look at each individual operation below.

Create Collection

Below is a simple example showing how to create a collection.  If no documentName is provided it will create a document with an auto generated ID.

Clicking on Create will update Firestore database with collection and document
// collection.service.ts
createCollection(collectionName: string, documentName?: string) {
    if (documentName) {
      try {
        this.afs
          .collection(collectionName)
          .doc(documentName)
          .set({ name: 'my value' })
      } catch(error) {
        console.error('createCollection', error);
      }
    } else {
      try {
        this.afs
          .collection(collectionName)
          .doc()
          .set({ name: 'my value' });
      } catch(error) {
        console.error('createCollection', error);
      }
    }
  }

Get Collection

Clicking on Create will subscribe to valueChanges and snapshotChanges of collection
// collection.service.ts
getCollection(collectionName: string, documentName?: string) {
    const collection = this.afs.collection(collectionName);

    if (documentName) {

      const docValue$: Observable<unknown> = collection.doc(documentName).valueChanges();
      docValue$.subscribe((res) => {
        console.log(`Document value changed ${collectionName}/${documentName}: ${res}`);
      });

      // will show the collection changes, modified, added, removed etc
      const docSnapshot$: Observable<unknown> = collection.doc(documentName).snapshotChanges();
      docSnapshot$.subscribe((res) => {
        console.log(`Document snapshot changed ${collectionName}/${documentName}: ${res}`);
      });

    } else {

      const value$: Observable<unknown[]> = collection.valueChanges();
      value$.subscribe((res) => {
        console.log(`Collection value changed ${collectionName}: ${res}`);
      });

      // will show the collection changes, modified, added, removed etc
      const snapshot$: Observable<DocumentChangeAction<any>[]> =
        collection.snapshotChanges();
      snapshot$.subscribe((res) => {
        console.log(`Collection snapshot changed ${collectionName}: ${res}`);
      });

    }
  }

Update Collection

Clicking on Update will update MyFirstDocument, note the data update is defined in the component
// collection.service.ts
updateDocument(collectionName: string, documentName: string, data: any) {
  this.afs.collection(collectionName).doc(documentName).update(data);
}

Delete Document

Clicking on Delete will delete MyFirstDocument,

Collection Service

The collection.service.ts contains all operations shown above.

import { Injectable } from '@angular/core';
import { AngularFirestore, DocumentChangeAction, } from '@angular/fire/compat/firestore';
import { Observable } from 'rxjs';

@Injectable()
export class CollectionService {

  allCollections = false;
  COLLECTION_LIST = 'collectionList';

  constructor(private afs: AngularFirestore) {
    if (!this.allCollections) {
      this.createCollectionList();
    }
  }

  /**
   * @param collectionName
   * @param documentName Optional
   * Create a collection.  If no documentName is provided a document
   * will be created with auto generated id.
   */
  createCollection(collectionName: string, documentName?: string) {
    if (documentName) {
      try {
        this.afs
          .collection(collectionName)
          .doc(documentName)
          .set({ name: 'my value' })
        this.addCollectionToList(collectionName);
      } catch(error) {
        console.error('createCollection', error);
      }
    } else {
      try {
        this.afs
          .collection(collectionName)
          .doc()
          .set({ name: 'my value' });
        this.addCollectionToList(collectionName);
      } catch(error) {
        console.error('createCollection', error);
      }
    }
  }

  /**
   * @param collectionName
   * @param documentName Optional
   * Shows how to subscribe to a collection or document.
   * valueChanges() - listen to all documents in collection
   * snapshotChanges() - listen to all documents in collection and their metadata, e.g. document ID
   */
  getCollection(collectionName: string, documentName?: string) {
    const collection = this.afs.collection(collectionName);

    if (documentName) {

      const docValue$: Observable<unknown> = collection.doc(documentName).valueChanges();
      docValue$.subscribe((res) => {
        console.log(`Document value changed ${collectionName}/${documentName}: ${res}`);
      });

      // will show the collection changes, modified, added, removed etc
      const docSnapshot$: Observable<unknown> = collection.doc(documentName).snapshotChanges();
      docSnapshot$.subscribe((res) => {
        console.log(`Document snapshot changed ${collectionName}/${documentName}: ${res}`);
      });

    } else {

      const value$: Observable<unknown[]> = collection.valueChanges();
      value$.subscribe((res) => {
        console.log(`Collection value changed ${collectionName}: ${res}`);
      });

      // will show the collection changes, modified, added, removed etc
      const snapshot$: Observable<DocumentChangeAction<any>[]> =
        collection.snapshotChanges();
      snapshot$.subscribe((res) => {
        console.log(`Collection snapshot changed ${collectionName}: ${res}`);
      });

    }
  }

  /**
   * @param collectionName
   * @param documentName
   * Update data in document
   */
  updateDocument(collectionName: string, documentName: string, data: any) {
    this.afs.collection(collectionName).doc(documentName).update(data);
  }

  /**
   * @param collectionName
   * @param documentName
   * Update data in document
   * NOTE: Deleting a collection requires coordinating an unbounded number of individual
   * delete requests. If you need to delete entire collections, do so only from a
   * trusted server environment. While it is possible to delete a collection from a
   * mobile/web client, doing so has negative security and performance implications.
   */
  deleteDocument(collectionName: string, documentName: string) {
    this.afs.collection(collectionName).doc(documentName).delete();
  }

  /**
   * Create a collection list.
   * This is for keeping track of all collections created.
   */
  private createCollectionList() {
    this.afs
      .collection(this.COLLECTION_LIST)
      .doc('list')
      .set({})
      .then(() => {
        this.allCollections = true;
      });
  }

  /**
   * @param collectionName
   * For each collection created a document with same name will be added
   * to the collection list.
   */
  private addCollectionToList(collectionName: string) {
    this.afs
      .collection(this.COLLECTION_LIST)
      .doc(collectionName)
      .set({ name: 'test' })
      .then(() => {
        this.allCollections = true;
      });
  }

  /**
   * Get all available collections.
   */
  getAllCollections() {
    this.afs
      .collection(this.COLLECTION_LIST)
      .valueChanges()
      .subscribe((res) => {
        console.log('All collections', res);
      });
  }
}

Wrap Up

This has been an basic introduction to getting up and running with Firebase and AngularFire.