Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use switchMap instead of nested subscriptions?

I'm still learning observables, so I won't be surprised if there's an easy solution. Basically what I have right now is four nested subscriptions with a fourEach inside the second subscribe(). I saw lots of answers using switchMap, but I couldn't find one that also had a for loop to iterate through. I know I probably should be using nested subscriptions, but I can't figure out how to do it with the forEach.

This is the working code with the nested subscribes:

dialogRef.afterClosed().subscribe(result => {
  if(result) {
    this.createLikertResponseGroup(result.likertResponseGroup)
      .subscribe(likertResponseGroupJSON => {

        result.likertResponse.controls.likertResponseFormArray.controls.forEach((element) => {
          let characteristic = element.controls.characteristic;
          this.newResponseGroupId = likertResponseGroupJSON.LikertResponseGroup.id;

          this.createLikertResponse(element, this.newResponseGroupId)
            .subscribe(likertResponseJSON => {

              if (characteristic) {
                let responseId = likertResponseJSON.LikertResponse.id;

                this.createCharacteristic(characteristic, this.newResponseGroupId, responseId)
                  .subscribe(characteristicJSON => {
                    this.newCharacteristicId = characteristicJSON.Characteristic.id;
                  });
              }
            });
        });
      })
  }
});

What I have works right now. So my question is, is it worth changing how I'm doing this? If so, how would I go about it?

I haven't gotten far, but my attempt with switchMap looks like this:

dialogRef.afterClosed().pipe(
  filter(result => result != null),
  switchMap(result => 
    from(result.likertResponse.controls.likertResponseFormArray.controls).pipe(
      // not sure what to do after this (or if I'm even doing it right)
    )
  ),
);
like image 306
thinlysliced Avatar asked Nov 17 '25 10:11

thinlysliced


1 Answers

mergeMap Instead of Nested subscribe

mergeMap does everything that a nested subscription does, but it also lets you continue your logic onward as it emits the subscribed values.


Quick aside:

In cases where your subscribed observable emits once and completes (like an http request), switchMap and mergeMap produce the same output. switchMap is often recommended over mergeMap in these cases. The reasons range from debugging memory leaks, to marginal performance, to what other developers expect.

For simplicity's sake, I've ignored that here and used mergeMap in all cases.


You can hide some complexity by nesting mergeMap and/or nesting subscriptions because you can rely on functional closures to set and remember values earlier in your pipeline.

It can also become a cause of great confusion down the line. Deeply nested functions are notoriously difficult to debug in JS so the extra effort of mapping into intermediate objects to hold the values you need in the next step (rather than nesting and getting intermediate values via functional closure) is well worth the effort.

It's also marginally faster as the runtime isn't required to travel up the call stack looking for variables (But again, you should do it because it's cleaner, maintainable, and extendable not in order to optimize early).

Here is your code litterally re-written with mergeMap and objects holding intermetiate values:

dialogRef.afterClosed().pipe(
  filter(result => result), // <-- only "truthy" results pass same as if(result)
  mergeMap(result =>
    this.createLikertResponseGroup(result.likertResponseGroup).pipe(
      map(likertResponseGroupJSON => ({result, likertResponseGroupJSON}))
    )
  ),
  mergeMap(({result, likertResponseGroupJSON}) => merge(
    ...result.likertResponse.controls.likertResponseFormArray.controls.map(
      element => this.createLikertResponse(
        element, 
        likertResponseGroupJSON.LikertResponseGroup.id
      ).pipe(
        map(likertResponseJSON => ({
          likertResponseJSON,
          characteristic: element.controls.characteristic,
          newResponseGroupId: likertResponseGroupJSON.LikertResponseGroup.id
        }))
      )
    )
  )),
  filter(({characteristic}) => characteristic) // only "Truthy" characteristic allowed
  mergeMap(({likertResponseJSON, characteristic, newResponseGroupId}) =>
    this.createCharacteristic(
      characteristic, 
      newResponseGroupId, 
      likertResponseJSON.LikertResponse.id
    ).pipe(
      map(characteristicJSON => ({
        newCharacteristicId: characteristicJSON.Characteristic.id,
        newResponseGroupId
      }))
    )
  )
).subscribe(({newCharacteristicId, newResponseGroupId}) => {
  this.newResponseGroupId = newResponseGroupId;
  this.newCharacteristicId = newCharacteristicId;
});

merge/forkJoin/concat Instead of forEach(stream.subscribe())

You'll notice in the code above that when it came time to re-write your forEach loop, I used a combination of merge and Array#map instead of Array#forEach

merge is the closes equivalent to forEach(stream.subscribe()), but the others can change up behaviour in ways that may even boost performance or just allow you to compose more complex streams intuitively.

Here, lines 2 and 3 have identical output. The second one, however, is easily extended with more RxJS operators

1. const arrayOfStreams = [s1,s2,s3,s4];
2. arrayOfStreams.forEach(s => s.subscribe(console.log));
3. merge(...arrayOfStreams).subscribe(console.log);

extending:

arrayOfStreams.forEach(s => s.subscribe(value => {
  if(this.isGoodValue(value)){
    console.log(value.append(" end"))
  }
}));

merge(...arrayOfStreams).pipe(
  filter(this.isGoodValue),
  map(value => value.append(" end"))
).subscribe(console.log);
like image 110
Mrk Sef Avatar answered Nov 19 '25 08:11

Mrk Sef



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!