adam nowak
adam nowak

Reputation: 815

How to do the chain sequence in rxjs

I would like to to something like:

this._myService.doSomething().subscribe(result => {
  doSomething()
});
.then( () => dosthelse() )
.then( () => dosanotherthing() )

So I would like to chain .then like in promise. How would I do that in Rxjs?

this._myService.getLoginScreen().subscribe( result => {
      window.location.href = MyService.LOGIN_URL;
      /// I would like to wait for the site to load and alert something from       the url, when I do it here it alerts the old one
    });
   .then (alert(anotherService.partOfTheUrl())


getLoginScreen() {
  return this.http.get(myService.LOGIN_URL)
.flatMap(result => this.changeBrowserUrl())
.subscribe( result => //i want to do sth when the page is loaded//);
}

changeBrowserUrl(): Observable<any> {
return Observable.create( observer => {
window.location.href = myService.LOGIN_URL;
observer.next();
});
}

Upvotes: 54

Views: 130093

Answers (4)

Kevin Baker
Kevin Baker

Reputation: 629

Example for Clarification (April, 2022)

The top of this pipe can emit n values (this means the chain will be called everytime a new value enters into the top of the pipe). In this example, n equals 3. This is a key difference between observables and promises. Observables can emit multiple values over time, but a promise cannot.

The subsequent chained streams emit one value (hence mimicing promises).

// Emit three values into the top of this pipe.
const topOfPipe = of<string>('chaining', 'some', 'observables');

// If any of the chained observables emit more than 1 value
// then don't use this unless you understand what is going to happen.
const firstObservable = of(1); 
const secondObservable = of(2);
const thirdObservable = of(3);
const fourthObservable = of(4);

const addToPreviousStream = (previous) => map(current => previous + current);
const first = (one) => firstObservable.pipe(addToPreviousStream(one));
const second = (two) => secondObservable.pipe(addToPreviousStream(two));
const third = (three) => thirdObservable.pipe(addToPreviousStream(three));
const fourth = (four) => fourthObservable.pipe(addToPreviousStream(four));

// Pipeline of mergeMap operators, used for chaining steams together.
topOfPipe.pipe(
  mergeMap(first),
  mergeMap(second),
  mergeMap(third),
  mergeMap(fourth),
).subscribe(console.log);

// Output: chaining1234 some1234 observables1234

You could also use concatMap or switchMap. They all have subtle differences. See rxjs docs to understand.

mergeMap: https://www.learnrxjs.io/learn-rxjs/operators/transformation/mergemap

concatMap: https://www.learnrxjs.io/learn-rxjs/operators/transformation/concatmap

switchMap: https://www.learnrxjs.io/learn-rxjs/operators/transformation/switchmap

Upvotes: 1

austinthedeveloper
austinthedeveloper

Reputation: 2601

Updated rxjs solution

Rxjs has changed quite a bit since this was answered.

  • flatMap is now mergeMap
    • Or switchMap, they're mostly interchangeable but it's good to know the difference
  • .do() is now tap()
  • Chaining is now done inside of a .pipe(). All manipulation should be done inside this pipe
    • You can chain pipes if needed (Ex. one variable maps an array of Users. Another variable takes that first variable and maps it a second time)

Do something after the original call has been made

Scenario

  • Make an HTTP call (Ex. Authentication check)
  • When that call has finished, navigate to another page
this._myService.getAuthenticated()
  .pipe(
    tap(result => this._myService.navigateToHome())
  )
  .subscribe()

Chain multiple calls

Scenario

  • Make an HTTP call (Ex. Authentication check)
  • Make a 2nd call to pull more info
  • Navigate after both calls have finished
this._myService.getAuthenticated()
  .pipe(
    // The Authentication call returns an object with the User Id
    switchMap(user => this._myService.getUserInfo(user.id))
    // After the user has been loaded, navigate
    tap(user => this._myService.navigateToHome())
  )
  .subscribe()

Note on the above examples: I am assuming these calls are HTTP which unsubscribe after being called once. If you use a live observable (ex. a stream of Users), make sure you either unsubscribe or use takeUntil/first operators.

Upvotes: 16

Thierry Templier
Thierry Templier

Reputation: 202346

If dosthelse or dosanotherthing returns a raw value, the operator to use is map. If it's an observable, the operator is flatMap (or equivalent).

If you want to do something imperatively. I mean outside the asynchronous processing chain, you could leverage the do operator.

Assuming that dosthelse returns an observable and dosanotherthing a raw object, your code would be:

this._myService.doSomething()
  .do(result => {
    doSomething();
  })
  .flatMap( () => dosthelse() )
  .map( () => dosanotherthing() );

Notice that if you return the return of the subcribe method, it will correspond to a subscription object and not an observable. A subscription object is mainly for being able to cancel the observable and can't take part of the asynchronous processing chain.

In fact, most of the time, you subscribe at the end of the chain.

So I would refactor your code this way:

this._myService.getLoginScreen().subscribe( result => {
  window.location.href = MyService.LOGIN_URL;
  /// I would like to wait for the site to load and alert something from       the url, when I do it here it alerts the old one
  alert(anotherService.partOfTheUrl()
});

getLoginScreen() {
  return this.http.get(myService.LOGIN_URL)
    .flatMap(result => this.changeBrowserUrl())
    .do( result => //i want to do sth when the page is loaded//);
}

changeBrowserUrl(): Observable<any> {
  return Observable.create( observer => {
    window.location.href = myService.LOGIN_URL;
    observer.next();
  });
}

Upvotes: 15

user3743222
user3743222

Reputation: 18665

The equivalent of then for observables would be flatMap. You can see some examples of use here :

For your example, you could do something like :

this._myService.doSomething()
  .flatMap(function(x){return functionReturningObservableOrPromise(x)})
  .flatMap(...ad infinitum)
  .subscribe(...final processing)

Pay attention to the types of what your functions return, as to chain observables with flatMap you will need to return a promise or an observable.

Upvotes: 85

Related Questions