0
votes

What is the correct way to write this without using a nested subscription?

The first observable will return a value that is passed to the second observable, then I need to use the returned values from both observables in the final subscription.

Also, the "doStuff" method is called from a button click, do I need to also unsubscribe from these observables to prevent memory leaks?

  doStuff() {

    // Get app user
    this.appUserService.getAppUser(true).subscribe((appUser) => {
      
      // Get login key
      this.accountApi.getLoginKey(appUser).subscribe(loginKey => {

        // Open in app browser
        this.browser = this.inAppBrowser.create(appUser.Url + loginKey, '_system');
      });
    });
  }

Edit 1

Tried the following as suggested but this does not compile, on the second pipe there is the following error:

Property 'pipe' does not exist on type 'OperatorFunction<AppUser, any>'

Then within the "map" function there is the following error:

Cannot find name 'appUser'.

   return this.appUserService.getAppUser(true).pipe(
      switchMap((appUser: AppUser) => this.accountApi.getUserLoginKey(appUser)).pipe(
        map(loginKey => { appUser, loginKey; }),
      ),
      tap(({ appUser, loginKey }) => {
        this.browser = this.inAppBrowser.create(appUser.Url + loginKey, '_system');
      }),
      take(1)
    );

Edit 2

I have now updated my solution as follows which works (but I could argue my original solution did work), so I have removed the nested subscriptions but they have been replaced with nested pipes instead. I believe this is a more acceptable solution (i.e. using nested "pipes" rather than nested "subscriptions") but not sure if it can be improved any further?

doStuff() {
       // Get app user
        this.appUserService.getAppUser(true).pipe(
          switchMap(appUser => this.accountApi.getLoginKey(appUser).pipe(
            tap(loginKey => {
              // Open in app browser as return URL
              this.browser = this.inAppBrowser.create(appUser.Url + loginKey, '_system');
            })
          )),
          take(1)
        ).subscribe();
}
3

3 Answers

1
votes

The "correct" way all depends on the specific behaviour you're looking for. If you only want one request to be alive at a time, use switchMap() or exhaustMap() (either new clicks will cancel the previous request or will be totally ignored respectively). Here is one way:

doStuff() {
  return this.appUserService.getAppUser(true).pipe(
    switchMap(appUser => this.accountApi.getLoginKey(appUser).pipe(
      map(loginKey => { appUser, loginKey }),
    )),
    tap(({ appUser: /* appUser type */, loginKey: /* loginKey type */ }) => {
      this.browser = this.inAppBrowser.create(appUser.Url + loginKey, '_system');
    }),
    
    // OPTIONAL: add this take(1) to make sure Observable completes after first emission
    // if that is the desired behaviour (one way to prevent leaks)
    take(1)
  );
}

UPDATE

Modified the code to remove the deprecated switchMap with resultSelector.

SECOND UPDATE

Corrected parentheses.

1
votes

There are two questions here,

  1. How to get around the nested subscription situation? @Will Alexander has answered that question. Here are some of the references that may help you
    https://angular-checklist.io/default/checklist/rxjs/Z1eFwa9
    https://www.thinktecture.com/en/angular/rxjs-antipattern-1-nested-subs/

  2. How to unsubscribe gracefully?
    a) You can unsubscribe manually from each of your subscriptions (this can get messy real fast)
    b) If you no longer need doStuff() stream, then you can use the take(1) as explained
    c) Angular recommends using async pipe wherever possible, so that the framework does the subscribe and unsubscribe part
    d) Use the takeUntil operator to unsubscribe from all you subscriptions (this comes in handy when you have many subscriptions that are not managed using async pipe) https://medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87

-1
votes

There is no "correct way", the code you have is already good. If you want to improve it a little, you could use rxjs (switchMap for example).

Regarding the unsubscribe part, it's always better and safer to unsubscribe. With Angular, you can use the async pipe in the HTML or simply unsubscribe in the destroy fase of the component.