Custom APIs

In order to interact with the Firestore Cloud database from our web application, we have to create APIs. In order to re-use these APIs wherever they may be needed, we can wrap them inside Angular services. As mentioned before, services are crucial for sharing data across components.

Fortunately, Firebase does a lot of the hard work for us -- we can wrap their APIs inside ours. Documentation for the full list of these can be found online; below are notable ones.

  1. Adding Data: Use the add method to create a new document in a collection.

this.firestore.collection('collectionName', ref => ref.orderBy('fieldName')).add({ field1: 'value1', field2: 'value2' })
  1. Reading Data: Use the valueChanges method to retrieve data from a collection or document.

this.firestore.collection('collectionName').valueChanges();
this.firestore.collection('collectionName').doc('documentId').valueChanges();
  1. Updating Data: Use the update method to update data in a document.

this.firestore.collection('collectionName').doc('documentId').update({ field1: 'newValue1', field2: 'newValue2' });
  1. Deleting Data: Use the delete method to delete a document.

this.firestore.collection('collectionName').doc('documentId').delete();
  1. Listening for Changes: Use the snapshotChanges method to listen for real-time updates.

this.firestore.collection('collectionName').snapshotChanges();
  1. Querying Data: Use various query methods like where, limit, startAt, and endAt to filter and sort data.

this.firestore.collection('collectionName', ref => ref.where('fieldName', '==', 'value').limit(1)).snapshotChanges();
  1. Transactions: Use the runTransaction method to perform atomic operations.

this.firestore.runTransaction(transaction => { /* transaction code */ });

Note that these methods return Observables. You can subscribe to these Observables to handle the data returned from Firestore.

Animals Service Example

// This function retrieves all animals from the 'Animal' collection in Firestore.
// It returns an Observable of the Animal array.
// Whenever the data changes in Firestore, the Observable emits a new value with the updated data.
getAnimals(): Observable<Animal[]> {
   return this.firestore.collection<Animal>('Animal').valueChanges().pipe(
     // The tap operator is used here to push the retrieved animals into the animalsSubject stream whenever they change.
     tap((animals: Animal[]) => {
       this.animalsSubject.next(animals);
     })
   );
}

// This function retrieves a specific animal by its ID from the 'Animal' collection in Firestore.
// It returns an Observable of the Animal object.
// If no animal is found with the given ID, it throws an error.
getAnimalById(id: string): Observable<Animal> {
   return this.firestore.collection<Animal>('Animal').doc(id).valueChanges().pipe(
     // The switchMap operator is used here to transform the emissions from the Observable.
     // If no animal is found, it throws an error. Otherwise, it returns an Observable of the animal.
     switchMap((animal: Animal | undefined) => {
       if (!animal) {
         throw new Error(`No animal found with id ${id}`);
       }
       return of(animal);
     })
   );
}

// This function selects a specific animal by its ID from the 'Animal' collection in Firestore.
// It doesn't return anything but triggers a side effect through the selectedAnimalSource Subject whenever the data changes in Firestore.
selectAnimal(id: string): void {
   this.firestore.collection<Animal>('Animal').doc(id).valueChanges().pipe(
     // The switchMap operator is used here to transform the emissions from the Observable.
     // If no animal is found, it returns an Observable of null. Otherwise, it returns an Observable of the animal.
     switchMap((animal: Animal | undefined) => {
       if (!animal) {
         return of(null);
       }
       return of(animal);
     })
   ).subscribe((animal: Animal | null) => {
     // The subscribed function pushes the selected animal into the selectedAnimalSource stream.
     this.selectedAnimalSource.next(animal);
   });
}

Last updated

Was this helpful?