1
votes

I have an Observable (commonSrv.uiMessage) which I am listening to. When it fires I show a message to the user (this.snackBar.open) and receive a ref (snackBarRef) as a result. When the user clicks on the message, onAcation() is fired and I receive a new Observable.

I want to take this Observable returned from snackBarRef.onAction() and listen to it somewhere else.

commonSrv.uiMessage.subscribe((msg) => {
          let snackBarRef = this.snackBar.open(msg.text , msg.actionTxt);
          snackBarRef.onAction().subscribe(() => {
            alert('The snack-bar action was triggered!');
          });
        })

I know I can't send an Observable from within a subscription, so this does not work

let x = new Observable<any>()
      commonSrv.uiMessage.subscribe((msg) => {
      let snackBarRef = this.snackBar.open(msg.text , msg.actionTxt);
      x = snackBarRef.onAction()
    });

    x.subscribe(a=> alert(a))

I should probably use pipe and map to funnel the original Observable but am not sure of the syntax or which operators are suitable (map? switchMap?)

2

2 Answers

2
votes

You should use switchMap operator in order to switch to a new observable after each subscription.

const onAction: Observable<any> = commonSrv.uiMessage.pipe(
   switchMap((msg: any) => {
      const snackBarRef = this.snackBar.open(msg.text, msg.actionTxt);
      return snackBarRef.onAction();
   ),
);

onAction.subscribe(
   () => {
      console.log('Click!');
   }
);

Shorter one:

const onAction: Observable<any> = commonSrv.uiMessage.pipe(
   switchMap((msg: any) => this.snackBar.open(msg.text, msg.actionTxt).onAction()),
);

onAction.subscribe(() => console.log('Click!'));
  • I recommend using more types for your functions params and variables.
  • Also very important to unsubscribe() your subscriptions when finish using them.
  • Notice that switchMap kills previous switchMaps subscriptions (HTTP requests for example) - if you wish to keep them "alive" - use mergeMap instead.

Addition: If you wish to receive both of the observable's values, you can use withLatestFrom operator:

const onAction: Observable<any> = commonSrv.uiMessage.pipe(
   withLatestFrom(this.snackBar.open(msg.text, msg.actionTxt).onAction()),
);

onAction.subscribe(([value1, value2]) => console.log(`first: ${value1} | second: {$value2}`));
1
votes

You thought in a right way. You need to use switchMap and map pipes. If I were you I would create the following

const snackBarAction$ = commonSrv.uiMessage.pipe(
  map(msg => this.snackBar.open(msg.text , msg.actionTxt)),
  switchMap(snackBarRef => snackBarRef.onAction()),
  shareReplay(1)
)

Since you subscribed on snackBarAction$, it is listening to uiMessage. And when such message received, snack bar would be opened and "switch" your subscription to onAction() changes.

Notice, that this.snackBar.open is kind of side effect, which can cause undesired behaviour: If you get several messages from uiMessage observable, this method would be called several times. You might need to close it every new message received. To cover this case you can use tap operator. If you want to reuse this observable somewhere else, another subscription would call all these pipe operators again. If it doesn't suits you, use shareReplay operator, it also preserves last emmited value.