Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 614
Nivedita Avatar asked Jan 02 '19 07:01

Nivedita


2 Answers

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 '$'.

like image 181
Jakob Segerslätt Avatar answered Nov 01 '22 01:11

Jakob Segerslätt


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>
like image 43
M.Mar Avatar answered Nov 01 '22 00:11

M.Mar