Home > Software design >  avoid nested subscribe if there is a forkjoin inside
avoid nested subscribe if there is a forkjoin inside

Time:03-01

Here is my code in angular

this.service.save(body).subscribe(
    resp => {
       this.dialog.confirmation({
          message: 'save object successfully!'
       })
       .subscribe((ok) => {
            if(ok) {
               this.pro.status = resp.status;
            this.loadingData(resp);
            const s1 = this.service.getSummary(this.id);
            const s2 = this.service.getCost(this.id);
            forkJoin([s1, s2]).subscribe([r1, r2]) => {
               this.view = r1;
               this.list = r2;
            }
          }
        });
     }
);

So there are many levels of subscribe. Not only it is ugly also the result is wrong and I can't not find it out by debugging. How can I rewrite it with rxjs operators?

CodePudding user response:

You can simplify it using the RxJS operators, like the following:

// import { EMPTY, forkJoin } from 'rxjs';
// import { map, mergeMap } from 'rxjs/operators';

this.service
  .save(body)
  .pipe(
    mergeMap((result) =>
      // Merge the main observable with the dialog confirmation one..
      // and map it to an object that contains the result from both observables.
      this.dialog
        .confirmation({ message: 'save object successfully!' })
        .pipe(map((confirmed) => ({ result, confirmed })))
    ),
    mergeMap(({ result, confirmed }) => {
      if (confirmed) {
        this.pro.status = result.status;
        this.loadingData(result);
        const s1 = this.service.getSummary(this.id);
        const s2 = this.service.getCost(this.id);
        return forkJoin([s1, s2]);
      }
      // Don't emit any value, if the dialog is not confirmed:
      return EMPTY;
    })
  )
  .subscribe(([r1, r2]) => {
    this.view = r1;
    this.list = r2;
  });

Note: To handle the memory leaks, it's highly recommended to unsubscribe from the observable when you don't need it anymore, and this can be achieved based on your use cases, such as assigning the subscribe function result to a Subscription variable and calling unsubscribe in ngOnDestroy lifecycle hook, or using a Subject with takeUntil operator and calling next/complete functions in ngOnDestroy.

And here is how to use the unsubscribe method for example:

// import { Subscription } from 'rxjs';

@Component({...})
export class AppComponent implements OnInit, OnDestroy {
    subscription: Subscription 
    ngOnInit(): void {
      this.subscription = this.service.save(body)
        // >>> pipe and other RxJS operators <<<
        .subscribe(([r1, r2]) => {
          this.view = r1;
          this.list = r2;
        });
    }
    ngOnDestroy() {
        this.subscription.unsubscribe()
    }
}

You can read more about that here: https://blog.bitsrc.io/6-ways-to-unsubscribe-from-observables-in-angular-ab912819a78f

CodePudding user response:

This should be roughly equivalent:

this.service.save(body).pipe(

  mergeMap(resp => 
    this.dialog.confirmation({
      message: 'save object successfully!'
    }).pipe(
      // This filter acts like your `if(ok)` statement. There's no 
      // else block, so if it's not okay, then nothing happens. The 
      // view isn't updated etc.
      filter(ok => !!ok),
      mapTo(resp)
    )
  ),

  tap(resp => {
    this.pro.status = resp.status;
    // If the following line mutates service/global state,
    // it probably won't work as expected
    this.loadingData(resp); 
  }),

  mergeMap(_ => forkJoin([
    this.service.getSummary(this.id),
    this.service.getCost(this.id)
  ]))

).subscribe([view, list]) => {
  this.view = view;
  this.list = list;
});
  • Related