403

I am trying to wrap my head around observables. I love the way observables solve development and readability issues. As I read, benefits are immense.

Observables on HTTP and collections seem to be straight forward. How can I convert something like this to observable pattern.

This is from my service component, to provide authentication. I'd prefer this to work like other HTTP services in Angular2 - with support for data, error and completion handlers.

firebase.auth().createUserWithEmailAndPassword(email, password)
  .then(function(firebaseUser) {
    // do something to update your UI component
    // pass user object to UI component
  })
  .catch(function(error) {
    // Handle Errors here.
    var errorCode = error.code;
    var errorMessage = error.message;
    // ...
  });

Any help here would be much appreciated. The only alternative solution I had was to create EventEmitters. But I guess that's a terrible way to do things in services section

frido
  • 13,065
  • 5
  • 42
  • 56
Krishnan Sriram
  • 5,037
  • 5
  • 21
  • 31

9 Answers9

600

If you are using RxJS 6.0.0:

import { from } from 'rxjs';
const observable = from(promise);
Liam
  • 27,717
  • 28
  • 128
  • 190
Guillaume
  • 6,214
  • 2
  • 13
  • 14
  • 23
    Using 6.3.3, `from` method returning observable but it is sending promise as value to subscriptions. :( – Laxmikant Dange Dec 17 '18 at 07:04
  • 1
    This answer is corrext for RXJS 6+. I tried to import from `operators` via "intuition" - I was wrong. – VSO Oct 30 '19 at 20:47
  • 9
    This answer is not correct, it would work only sometimes. Promises are eager, and observables are lazy (don't start until subscribe). With this solution, the promise has already started even there is no '.subscribe()', so it does not behave like an observable. See answer https://stackoverflow.com/a/69360357/6099651 for a better solution. – Llorenç Pujol Ferriol Mar 21 '22 at 12:39
  • 3
    Please be careful with this solution, Because it may result in an unhandled promise rejection which can break your code! If you are using from operator you must handle your promise rejection in the promise within the from operator(using .catch()), otherwise the promise may execute and throw an error, while its wrapping observable hasn't been subscribed yet, and if your error handling is piped to the observable it will not catch the error ! (I prefer using defer instead and handling the errors centrally from the observable). – coderrr22 Oct 26 '22 at 20:10
208

1 Direct Execution / Conversion

Use from to directly convert a previously created Promise to an Observable.

import { from } from 'rxjs';

// getPromise() is called once, the promise is passed to the Observable
const observable$ = from(getPromise());

observable$ will be a hot Observable that effectively replays the Promises value to Subscribers.

It's a hot Observable because the producer (in this case the Promise) is created outside of the Observable. Multiple subscribers will share the same Promise. If the inner Promise has been resolved a new subscriber to the Observable will get its value immediately.

2 Deferred Execution On Every Subscribe

Use defer with a Promise factory function as input to defer the creation and conversion of a Promise to an Observable.

import { defer } from 'rxjs';

// getPromise() is called every time someone subscribes to the observable$
const observable$ = defer(() => getPromise());

observable$ will be a cold Observable.

It's a cold Observable because the producer (the Promise) is created inside of the Observable. Each subscriber will create a new Promise by calling the given Promise factory function.

This allows you to create an observable$ without creating and thus executing a Promise right away and without sharing this Promise with multiple subscribers. Each subscriber to observable$ effectively calls from(promiseFactory()).subscribe(subscriber). So each subscriber creates and converts its own new Promise to a new Observable and attaches itself to this new Observable.

3 Many Operators Accept Promises Directly

Most RxJS operators that combine (e.g. merge, concat, forkJoin, combineLatest ...) or transform observables (e.g. switchMap, mergeMap, concatMap, catchError ...) accept promises directly. If you're using one of them anyway you don't have to use from to wrap a promise first (but to create a cold observable you still might have to use defer).

// Execute two promises simultaneously
forkJoin(getPromise(1), getPromise(2)).pipe(
  switchMap(([v1, v2]) => v1.getPromise(v2)) // map to nested Promise
)

Check the documentation or implementation to see if the operator you're using accepts ObservableInput or SubscribableOrPromise.

type ObservableInput<T> = SubscribableOrPromise<T> | ArrayLike<T> | Iterable<T>;
// Note the PromiseLike ----------------------------------------------------v
type SubscribableOrPromise<T> = Subscribable<T> | Subscribable<never> | PromiseLike<T> | InteropObservable<T>;

The difference between from and defer in an example: https://stackblitz.com/edit/rxjs-6rb7vf

const getPromise = val => new Promise(resolve => {
  console.log('Promise created for', val);
  setTimeout(() => resolve(`Promise Resolved: ${val}`), 5000);
});

// the execution of getPromise('FROM') starts here, when you create the promise inside from
const fromPromise$ = from(getPromise('FROM'));
const deferPromise$ = defer(() => getPromise('DEFER'));

fromPromise$.subscribe(console.log);
// the execution of getPromise('DEFER') starts here, when you subscribe to deferPromise$
deferPromise$.subscribe(console.log);

defer is probably the operator most people are looking for as many apps rely on Observables to be cold and trigger a data fetch on subscribe. from is still a viable option for certain use cases though, e.g. when you want to create a Promise once during some initialisation process and then propagate its value via an Observable that will be subscribed to multiple times, but don't want to create and execute the Promise again for every subscriber.

frido
  • 13,065
  • 5
  • 42
  • 56
142

try this:

import 'rxjs/add/observable/fromPromise';
import { Observable } from "rxjs/Observable";

const subscription = Observable.fromPromise(
    firebase.auth().createUserWithEmailAndPassword(email, password)
);
subscription.subscribe(firebaseUser => /* Do anything with data received */,
                       error => /* Handle error here */);

you can find complete reference to fromPromise operator here.

Patricio Vargas
  • 5,236
  • 11
  • 49
  • 100
Godfather
  • 5,711
  • 5
  • 21
  • 27
63

The correct pattern to transform a promise into an observable is using defer and from operators:

import { defer, from } from 'rxjs';
    
const observable$ = defer(() => from(myPromise()));

Why we need the defer operator?

Promises are eager, this means that when called they fire instantly. This is the opposite from how observables work. Observables are lazy, they are only fired when .subscribe() is called. This is the reason we need to always wrap it into a defer operator. The from operator doesn't do this work, so defer is always needed.

  • I am failing to see a scenario where using only `from` fails. Can you share an example where this would behave differently? – Tobias S. Oct 30 '22 at 01:06
  • Here is an example: https://typescript-hhrcxj.stackblitz.io In this example we should start the Promise on .subscribe to work, and for that is compulsory the defer() operator. Without the defer operator, subscribe is not needed to launch the Promise. So in some cases the Promise is already resolved before subscription, that is a pattern to avoid. – Llorenç Pujol Ferriol Nov 01 '22 at 19:05
  • @TobiasS. I had another example right now. One signalR call returns a "errorResult" event if not failing, so I had to replay the call. The rxJs operator replay was not working, since the promise was already executed. With defer(()=>from), the signalR call gets replayed. – Marcus Kaseder Feb 03 '23 at 15:42
4

You can add a wrapper around promise functionality to return an Observable to observer.

  • Creating a Lazy Observable using defer() operator which allows you to create the Observable only when the Observer subscribes.
import { of, Observable, defer } from 'rxjs'; 
import { map } from 'rxjs/operators';


function getTodos$(): Observable<any> {
  return defer(()=>{
    return fetch('https://jsonplaceholder.typicode.com/todos/1')
      .then(response => response.json())
      .then(json => {
        return json;
      })
  });
}

getTodos$().
 subscribe(
   (next)=>{
     console.log('Data is:', next);
   }
)

khizer
  • 1,242
  • 15
  • 13
4
import { from } from 'rxjs';

from(firebase.auth().createUserWithEmailAndPassword(email, password))
.subscribe((user: any) => {
      console.log('test');
});

Here is a shorter version using a combination of some of the answers above to convert your code from a promise to an observable.

Jonathan
  • 3,893
  • 5
  • 46
  • 77
  • 3
    While this code may solve the question, [including an explanation](//meta.stackexchange.com/q/114762) of how and why this solves the problem would really help to improve the quality of your post, and probably result in more up-votes. Remember that you are answering the question for readers in the future, not just the person asking now. Please [edit] your answer to add explanations and give an indication of what limitations and assumptions apply. – janw Nov 01 '20 at 08:41
3

You may also use defer. The main difference is that the promise is not going to resolve or reject eagerly.

Mateja Petrovic
  • 3,799
  • 4
  • 25
  • 40
2

You can also use a Subject and trigger its next() function from promise. See sample below:

Add code like below ( I used service )

class UserService {
  private createUserSubject: Subject < any > ;

  createUserWithEmailAndPassword() {
    if (this.createUserSubject) {
      return this.createUserSubject;
    } else {
      this.createUserSubject = new Subject < any > ();
      firebase.auth().createUserWithEmailAndPassword(email,
          password)
        .then(function(firebaseUser) {
          // do something to update your UI component
          // pass user object to UI component
          this.createUserSubject.next(firebaseUser);
        })
        .catch(function(error) {
          // Handle Errors here.
          var errorCode = error.code;
          var errorMessage = error.message;
          this.createUserSubject.error(error);
          // ...
        });
    }

  }
}

Create User From Component like below

class UserComponent {
  constructor(private userService: UserService) {
    this.userService.createUserWithEmailAndPassword().subscribe(user => console.log(user), error => console.log(error);
    }
  }
Shivang Gupta
  • 3,139
  • 1
  • 25
  • 24
  • Subjects are low-level machinery. Don't use subjects, except for the cases when you're extending `rxjs`. – polkovnikov.ph Dec 10 '18 at 18:52
  • I am just giving a solution. – Shivang Gupta Dec 12 '18 at 17:23
  • You could have at least shown `new Observable(observer => { ... observer.next() ... })` way to implement it. Even though it would be a reimplementation of existing well-known function, it would directly answer the question and wouldn't be harmful to readers. – polkovnikov.ph Dec 13 '18 at 00:41
-3

There is toPromise() operator provided by Rxjs , Like the code example demonstrates :

@Injectable({
  providedIn: 'root'
})
export class InventoryService {
  constructor(private httpClient: HttpClient) {}

  getCategories(): Observable<Category[]> {
    const url = 'https://www.themealdb.com/api/json/v1/1/categories.php';

    return this.httpClient.get<CategoriesResponse>(url).pipe(
      map(response => response.categories)
    );
  }
}

And inside your component, you can apply the toPromise() operator :

export class AppComponent {
  categories: any[];

  constructor(private inventoryService: InventoryService) {}

  public async loadCategories() {
    this.categories = await this.inventoryService
      .getCategories()
      .**toPromise()**

But currently with Rxjs7+ is deprecated and it's recommended to use lastValueFrom() operator :

  public async loadCategories() {
    const categories$ = this.inventoryService.getCategories();
    this.categories = await **lastValueFrom**(categories$);
  }

I hope it helps with an updated code with the updated version :')

Torsten Barthel
  • 3,059
  • 1
  • 26
  • 22
Rebai Ahmed
  • 1,509
  • 1
  • 14
  • 21