Tomas Lukac
Tomas Lukac

Reputation: 2225

Angular 9 return Observable from an Observable

I am looking for a way to return an Observable from another Observable. I found that you can do that using pipe and map operators, but it does not seem to be working for me. What am I doing wrong ?

I am using Angular 9.1.12 and rxjs ~6.5.4.

Example: Service1

import { Observable, of } from 'rxjs';

export class Service1 {

  test(): Observable<string> {
      console.log(1);
      return of('hey');
  }
}

Service2

import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

export class Service2 {

  constructor(private service1: Service1) {}

  test(): Observable<string> {
    return this.service1.test().pipe(
      map((value: string) => {
        console.log(2);
        return value;
      })
    );
  }
}

Component

import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

export class Component implements OnInit {

  constructor(private service2: Service2) {}

  test(): Observable<void> {
    return this.service2.test().pipe(
      map((value: string) => {
        console.log(3);
      }));
    }
  }
}

The console will only output 1.

Upvotes: 0

Views: 3323

Answers (3)

Barremian
Barremian

Reputation: 31125

There are two types of observables: hot and cold. I am not going into hot observables since it doesn't have anything to do with the question. You could find more information here

Cold observables - as the name implies - do not start processing it's inner statements until it is subscribed to. So in this case when you subscribe in the component, it would trigger all the observables upto to the inner most of('hey').

export class Component implements OnInit {
  constructor(private service2: Service2) {}

  ngOnInit() {
    this.test().subscribe();
  }

  test(): Observable<void> {
    return this.service2.test().pipe(
      tap((value: string) => console.log(value))
    );
  }
}

One more thing to note here, you were using map operator in the component without a return statement. In that case, it would return undefined. map is generally used to transform the incoming statements. tap operator would be a better fit here. It's used to perform side-effects.

Unsubscribe

Futhermore, open subscriptions do not close unless an error is emitted or explicitly completed. So it's always a good idea to close open subscriptions after they are used.

Eg. in Angular it's common practice to close it in the ngOnDestroy hook so that it's closed when the component is closed.

export class Component implements OnInit, OnDestroy {
  sub: Subscription;

  constructor(private service2: Service2) {}

  ngOnInit() {
    this.sub = this.test().subscribe();
  }

  test(): Observable<void> {
    return this.service2.test().pipe(
      tap((value: string) => console.log(value))
    );
  }

  ngOnDestroy() {
    if (!!this.sub)
      this.sub.unsubscribe();
  }
}

There are more elegant ways to close open subscriptions. See here: https://stackoverflow.com/a/60223749/6513921

Upvotes: 1

Mrk Sef
Mrk Sef

Reputation: 8022

Just as functions only run the code inside when you invoke them, Observables only run their code when you you subscribe to them.

You see 1 output because you invoke this.service1.test().

You don't see 2 or 3 because you never subscribe to those Observables.

export class Component implements OnInit {

  constructor(private service2: Service2) {}

  test(): void {
    this.service2.test().pipe(
      map(_ => console.log(3))
    ).subscribe();
  }
  
}

Upvotes: 1

StPaulis
StPaulis

Reputation: 2916

That's reasonable cause you never subscribe to the observables so they never actual emitted or run.

You should subscribe on the component like this.

this.test().subscribe();

I have created a stackblitz to play around.

PS: Be aware that you have to unsubscribe when needed also. If you are not familiar with these concepts I suggest you to read that article.

Upvotes: 3

Related Questions