Angularfire: Convert push and other operations to return Observables

Created on 2 Feb 2017  路  6Comments  路  Source: angular/angularfire

Most Angular 2 developers are writing applications and using libraries that take advantage of the observable pattern more than promises. Because of this, I suggest that the push, remove, and update parameters be changed to return Observables rather than "Thenable" and "firebase.Promise"

As far as converting promises to Observables, it's possible, but with Thenables I don't believe there is any easy support besides hand crafting an Observable yourself.

I am mainly requesting this feature because of the popularity of Observables and to take advantage of additional features from Observables. (If you're using Observables throughout your application, using a promise in one area doesn't make much sense if it can be implemented using Observables)

Version info

Angular: 2.4

Firebase: 3.6.7

AngularFire:2.0.0-beta.7

Other (e.g. Ionic/Cordova, Node, browser, operating system): Windows 10

Most helpful comment

The likely difference is that I am using core-js and @types/core-js and its declaration for Promise<T> is compatible with Firebases's Thenable. I guess you are using something else.

It's a purely TypeScript issue, but I sympathise with your predicament. I see it quite a bit when answering Stack Overflow questions that involve TypeScript.

The good news is that it's really easy to write your own RxJS static fromThenable operator. You could define one like this (in a file named fromThenable.ts):

import { Observable } from "rxjs/Observable";
import { IScheduler } from "rxjs/Scheduler";
import { fromPromise } from "rxjs/observable/fromPromise";
import * as firebase from "firebase";

function staticFromThenable<T>(thenable: firebase.Thenable<T>, scheduler?: IScheduler): Observable<T> {
  const anything: any = thenable;
  return fromPromise(anything as Promise<T>, scheduler);
}

Observable.fromThenable = staticFromThenable;

declare module "rxjs/Observable" {
  namespace Observable {
    export let fromThenable: typeof staticFromThenable;
  }
}

And you'd consume it like this:

import { Observable } from "rxjs/Observable";
import "./fromThenable";

let thenable = angularFire.database.list("some/data").push({ some: "data" });
let observable = Observable.fromThenable(thenable);

All 6 comments

I'm not in favour of this.

Firstly, because it would be a breaking change.

Secondly, it's not as simple as just using the fromPromise operator internally to convert the resultant promise to an observable.

Observables are supposed to be lazy. If a method returns an observable - as with Angular's Http service - the caller could reasonably expect nothing to occur until subscribe is called on the returned observable. If fromPromise were used, that would not be the case with push, et al., as the data would be pushed before - and regardless of - any subsequent subscribe call.

And if a more complicated internal implementation were to be used to make the push call lazy, what would be the meaning of multiple subscribe calls? Multiple pushes? Wouldn't that be confusing?

I think things are clearer and simpler as they are. If you want an observable, just use the fromPromise operator. Also, be aware that promises are understood by RxJS - that is, you can return a promise (instead of an observable) to operators without having to call fromPromse. For example:

http
  .get('some-url')
  .map(response => response.json())
  .mergeMap(data => angularFire.database.list('received-data').push(data))
  .do(() => console.log('Pushed some data successfully.'))
  .catch((error) => console.log(error));

Anyway, that's my two cents.

OK, well the issue with using fromPromise is that push returns a "ThenableReference" instead of a normal promise. And there's no method "Observable.fromThenableReference"

Well, whether or not a Thenable is a promise is going to depend upon what your definition of a promise is.

If your definition is Promises/A+, then I suspect they are promises.

If your definition is a Promise<T> from TypeScript's perspective, then they aren't.

For what it's worth, I created a couple of behaviour tests to make sure that what I said wasn't utter rubbish. The following work fine for me with Firebase 3.6.9, AngularFire2 2.0.0-beta.7, RxJS 5.1.0 and TypeScript 2.1.6:

describe("thenable behaviour", () => {

  it("should be compatible with fromPromise", () => {

    let list = angularFire.database.list("temp/sequence");
    let thenable = list.push({ pushed: true });
    let observable = Observable.fromPromise(thenable);

    return observable
      .toPromise()
      .then((result) => {
        expect(result).to.exist;
        expect(result.key).to.equal(thenable.key);
      });
  });

  it("should be returnable to operators", () => {

    let list = angularFire.database.list("temp/sequence");

    return Observable.of(null)
      .mergeMap(() => list.push({ pushed: true }))
      .toPromise()
      .then((result) => { expect(result).to.exist; });
  });
});

@cartant Thanks for following up. Unfortunately, I'm running the same version of firebase, TypeScript, and rxjs and I get an error in Visual Studio saying
"Argument of type 'ThenableReference' is not assignable to parameter of type 'Promise'.
Property '[Symbol.toStringTag]' is missing in type 'ThenableReference'."

The likely difference is that I am using core-js and @types/core-js and its declaration for Promise<T> is compatible with Firebases's Thenable. I guess you are using something else.

It's a purely TypeScript issue, but I sympathise with your predicament. I see it quite a bit when answering Stack Overflow questions that involve TypeScript.

The good news is that it's really easy to write your own RxJS static fromThenable operator. You could define one like this (in a file named fromThenable.ts):

import { Observable } from "rxjs/Observable";
import { IScheduler } from "rxjs/Scheduler";
import { fromPromise } from "rxjs/observable/fromPromise";
import * as firebase from "firebase";

function staticFromThenable<T>(thenable: firebase.Thenable<T>, scheduler?: IScheduler): Observable<T> {
  const anything: any = thenable;
  return fromPromise(anything as Promise<T>, scheduler);
}

Observable.fromThenable = staticFromThenable;

declare module "rxjs/Observable" {
  namespace Observable {
    export let fromThenable: typeof staticFromThenable;
  }
}

And you'd consume it like this:

import { Observable } from "rxjs/Observable";
import "./fromThenable";

let thenable = angularFire.database.list("some/data").push({ some: "data" });
let observable = Observable.fromThenable(thenable);

nice explanation. Its working from my end.@cartant

Was this page helpful?
0 / 5 - 0 ratings