# 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.

```typescript
this.firestore.collection('collectionName', ref => ref.orderBy('fieldName')).add({ field1: 'value1', field2: 'value2' })
```

2. **Reading Data**: Use the **valueChanges** method to retrieve data from a collection or document.

```typescript
this.firestore.collection('collectionName').valueChanges();
this.firestore.collection('collectionName').doc('documentId').valueChanges();
```

3. **Updating Data**: Use the **update** method to update data in a document.

```typescript
this.firestore.collection('collectionName').doc('documentId').update({ field1: 'newValue1', field2: 'newValue2' });
```

4. **Deleting Data**: Use the **delete** method to delete a document.

```typescript
this.firestore.collection('collectionName').doc('documentId').delete();
```

5. **Listening for Changes**: Use the **snapshotChanges** method to listen for real-time updates.

```typescript
this.firestore.collection('collectionName').snapshotChanges();
```

6. **Querying Data**: Use various query methods like **where, limit, startAt, and endAt** to filter and sort data.

```typescript
this.firestore.collection('collectionName', ref => ref.where('fieldName', '==', 'value').limit(1)).snapshotChanges();
```

7. **Transactions**: Use the **runTransaction** method to perform atomic operations.

```typescript
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**

```typescript
// 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);
   });
}
```
