How to get an Observable for data from Firestore query in Angular 7?

11,963

Solution 1

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.

In your service do: (I've declared the collection and user$ variables for clarity).

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$;
}

If you want the ID as well on your user 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 (in my opinion).

// Query the users by a specific email and return the first User with ID added    
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;
    }
  }));

In your Component code you can call this by

checkEmail() {
this.user$ = this.us.getUserByEmail('[email protected]')
  .pipe(
    tap(user => {
      if (user) {
        this.msg = 'success';
      } else {
        this.msg = 'User with this email does not exist!';
      }
    }));
}

Note that the user will be null until you call subscribe on this.user$ OR in your html use the async pipe which handles subscribe and unsubscribe on a observable.

<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

A note on naming conventions. Observables' variable names are often suffixed with '$', while other (string, numbers etc) don't have the '$'.

Solution 2

Create a service:

   constructor(private db: AngularFirestore) { }

      getCategories() {
        return this.db.collection('categories').valueChanges();
      }

use it:

 categories$;

  constructor(categoryService: CategoryService) {
    this.categories$ = categoryService.getCategories();
  }

display your documents:

 <select id="category" type="text" class="form-control">
        <option value=""></option>
        <option *ngFor="let c of categories$ | async" [value]="c.$key">
          {{ c.name }}
        </option>
    </select>
Share:
11,963
Nivedita
Author by

Nivedita

Updated on June 09, 2022

Comments

  • Nivedita
    Nivedita almost 2 years

    I have method in my service, inside which I am trying to return an Observable of the User object. Here is the my code -

     constructor(private firestore: AngularFirestore,
        private db: AngularFireDatabase) { }
    
     /**
       * get user with given email, if not return null
       * 
       * @param email email to fetch
       */
      getUserByEmail(email: string): Observable<User> {
    
        var user: User;
    
        let userRef = this.firestore.collection("users").ref.where('email', '==', email);
    
        userRef.get().then(res => res.forEach(userDoc => {
    
          user = userDoc[0].data() as User; // since email IDs are unique, I want the 0th element.
          user.id = userDoc[0].id;
    
          console.log(user); // has the data
    
          // return user; // doesn't work, not the right thing
    
        }));
    
        console.log(user); // undefined, since call is async
    
        return of(user);
      }
    

    Inside the component, I want that data for the next steps, so I did this -

    checkEmail() {
    
        var user$: User
    
        this.us.getUserByEmail(this.existingUserForm.value.email).subscribe(user => {
    
          console.log(user);
    
          if (user) {
    
              this.msg$ = "success";
              // next steps
          }
          else {
            this.msg$ = "User with this email does not exist!";
          }
    
        });
    
      }
    

    I am not sure how to return an observable from my service, so that I can use the data in my component. And is this the right way to do what I am trying to?

  • Nivedita
    Nivedita over 5 years
    Thanks for the answer, but I also want the ID of the user, which valueChanges does not return, eill this work if i use snapshot changes?
  • Jakob Segerslätt
    Jakob Segerslätt over 5 years
    Correct, that's a little more tricky but i'll edit the original answer to show the code.