Angular 2 Firebase Observable to promise doesn't return anything
Asked Answered
B

2

17

I'm currently working on an Angular 2 project with AngularFire2 and I'm trying to convert a FirebaseListObservable to a Promise. I know that it doesn't make much sense since Observables are more useful but this function will be part of another function where multiple promises are chained. And I'm not familiar with how to subscribe to Observables within a chain of promises... The function gets executed in the service, however it seems to not return anything. Basically, what I want to do is check in a Firebase list if an object with a certain name already exists and return either true or false.

Service

constructor(private _af: AngularFire) { }

nameExists(name: string): Promise<boolean> {

 return this._af.database.list('users')
  .map(users => {

    let exists = false;
    users.forEach(user => {
      if(user.name.toLowerCase() === name.toLowerCase()) {
        console.log('Name already exists!');
        exists = true;
      }
    });
    return exists;
  }).toPromise();
}

component

constructor(private _usersService: UsersService) { }

check(name) {
 this._usersService.nameExists(name)
  .then(bool => console.log(bool));
}

So the function gets executed and seems to work correctly as it prints to the console, when there's a match. However, console.log() in the component does not get executed. I guess the "then" part is never reached. On a separate note, is there a way to stop the forEach loop once a match is found?

Any help would be greatly appreciated as I couldn't find any answers at all to this.

Bradfield answered 27/1, 2017 at 0:8 Comment(2)
in your component is _dibbService suppose to be _usersService?Latvina
Yes, sorry! I switched them up when copying the code over.Bradfield
N
35

The problem is that the toPromise operator converts the observable to a promise that resolves to the observable's final value. That means the observable must complete before the promise resolves.

In AngularFire2, list and object observables don't complete; they re-emit whenever the database changes.

You can solve the problem using the first operator, which takes the first emitted value and then completes the composed observable:

import 'rxjs/add/operator/first';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';
...
return this._af.database
  .list('users')
  .map(users => {

    let exists = false;
    users.forEach(user => {
      if (user.name.toLowerCase() === name.toLowerCase()) {
        console.log('Name already exists!');
        exists = true;
      }
    });
    return exists;
  })
  .first()
  .toPromise();
Nilsson answered 27/1, 2017 at 0:34 Comment(6)
Just when you thought you've used Observables enough to understand them... Thank you so much! It worked!Bradfield
When using the .first(), is the subscription lost?Theatricals
@Luis No, first is a 'normal' operator and a subscribe call is still required. However, toPromise isn't. It involves an implicit call to subscribe, as it converts the observable into a promise. The returned promise resolves when the observable completes. Also, when an observable completes, all subscribers are automatically unsubscribed.Nilsson
why first and not take(1) for example?Cloakroom
@Jimmy If the source completes without emitting a value, first will error - take(1) won't. See https://mcmap.net/q/115854/-take-1-vs-first. Unless I expect the source to sometimes complete without emitting, I favour using first.Nilsson
Where can I read more to fully understands this? if Firebase never completes (it keeps emitting success) then the promise will never resolve ?!!Elroyels
S
0

I solved this by unsubscribing it after the first data received lazy way :)

const userSubs$ = this.db.object(`branch/${branchId}/products`).subscribe(
                     async (payload: any) => {
                      userSubs$.unsubscribe();
                       })

Or convert your code to something like this good way :)

 const eventref = this.db.database.ref(`branch/${branchId}/products`);
  const snapshot = await eventref.once('value');
  const productArray = snapshot.toJSON()
  return productArray;

Happy Coding

Sholem answered 2/3, 2021 at 12:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.