Search code examples
javascriptfirebasegoogle-cloud-firestoreobservableangularfire5

Querying data from Firestore as observable


I am using this request to get my documents from Firestore asynchronously.

During an operation (delete for example) the list of my documents is not updated automatically. How can I transform my asynchronous function into an observable in order to take advantage of the real-time functionalities of Firestore and get the id of the document ?

import { Injectable } from '@angular/core';
import { SentencePair } from '../models/sentence-pair.model';
import { Firestore, collectionData, deleteDoc,limit, limitToLast,orderBy, increment, 
         addDoc, collection, doc, updateDoc, setDoc,query, where, getDocs } from 
        '@angular/fire/firestore';
import { Observable,combineLatest,map, defer } from 'rxjs';

@Injectable({ providedIn: 'root'})

export class SentencesPairsService {

constructor(private firestore: Firestore) { }

async FilterPairs(field: string, boolean: boolean, display:any) { 
  const sentencepairsRef = collection(this.firestore, 'SentencesPairs');
  const q = query(sentencepairsRef,
              where('validation', '==', boolean),
              where('origin_trad', 'in', display),
              orderBy(field),limitToLast(10));
  const truc = await getDocs(q)
  return truc.docs.map(docData=>({...docData.data(), id:docData.id}));
  }

I use AngularFire 7.2. Thanks for your help.


Solution

  • Get an observable for data from Firestore query

    If you want an observable from Firestore, you need to return the .valueChanges() on a AngularFirestoreCollection. Read this doc for reference: https://github.com/angular/angularfire2/blob/master/docs/firestore/querying-collections.md.

    For example :

    getUserByEmail(email: string): Observable<User> { 
    const collection = this.firestore.collection<User>('users', ref => ref.where('email', '==', email)) 
    const user$ = collection 
    .valueChanges()
     .pipe( map(users => { 
    const user = users[0]; 
    console.log(user); 
    return user; 
    })); 
    return user$; 
    }
    

    Get ID of the document

    If you want the ID( which is not returned in valueChanges())on your document you need to use snapshotChanges(). Sometimes it's easier to maintain the id on the user data when saving to Firestore to avoid using snapshotChanges. Also SwitchMap is helpful in this case as when a new value comes from the source (userId in this case) it will cancel its previous firestore subscription and switch into a new one with the new user id. If for some reason you want to maintain firestore subscriptions for all userIds that come through at once, then use mergeMap instead. But typically you only want to be subscribed to one user's data at a time.

    // Query the users by a specific email and return the first User with ID added using snapshotChanges()

    return this.firestore.collection<User>('users', ref => ref.where('email', '==', email))
     .snapshotChanges()
     .pipe(map(users => { 
    const user = users[0];
    if (user) { 
    const data = user.payload.doc.data() as User; 
    const id = user.payload.doc.id; 
    return { id, ...data }; 
    } 
    else { return null; 
    } 
    }));
    

    Note to subscribe to the changes :

    The user will be null until you call subscribe on this.user$ :

    const queryObservable = size$.pipe(
      switchMap(size => 
        afs.collection('items', ref => ref.where('size', '==', size)).snapshotChanges()
      )
    );
    queryObservable.subscribe(queriedItems => {
      console.log(queriedItems);  
    });
    

    OR

    In your html use the async pipe which handles subscribe and unsubscribe on an observable like below:

    <div *ngIf="(user$ | async) as user">{{ user.email }}</div>
    

    Try to avoid using the async pipe on the same observable multiple times in your html, and instead set it as a local html variable which i do above (as user) to avoid unnecessary data trips to the db.