0
\$\begingroup\$

Bad Practice: Nested Observables

I could use some help with preventing nested Observables, by using higher order ones somehow.

I have some starting data (expected results are behind it in comments):

export const ITEMS : Item[] = [
  {
    check1: 'aBlaat', // Starts with a, true
    check2: 'aBliet', // Starts with a, true
    value: '100'
  },
  {
    check1: 'aBlaat', // Starts with a, true
    check2: 'bBliet', // Starts with b, false
    value: '101'
  },
  {
    check1: 'bBlaat', // Starts with b, false
    check2: 'aBliet', // Starts with a, true
    value: '102'
  },
  {
    check1: 'bBlaat', // Starts with b, false
    check2: 'bBliet', // Starts with b, false
    value: '103'
  },
  {
    check2: 'bBliet', // Starts with b, false
    value: '104'
  }, // No check1, true
  {
    check2: 'aBliet', // Starts with a, true
    value: '105'
  }, // No check1, true
]

There are 2 back end services (these are simplified, but the idea is that they do a check and return a boolean Observable. In reality these checks don't just check the first letter of the string, but they really need a back end call).

firstCheck(item: Item) {
  return of(item.check1.startsWith('a'));
}

secondCheck(item: Item) {
  return of(item.check2.startsWith('a'))
}

Here is the component:

public resultingArray : Item[] = []

constructor(
  private checkingService: CheckingService
) {}

ngOnInit() {
  const firstCheckObservables: Observable<boolean>[] = []
  let items = ITEMS
  items.forEach(item => {
    firstCheckObservables.push(item.check1 ? this.checkingService.firstCheck(item).pipe(take(1)) : of(true))
  })

  forkJoin(firstCheckObservables)
  .pipe(
    map(booleanArray => this.setFirstFlags(items, booleanArray)),
    map(firstCheckItems => {
      return this.extractNeedsSecondFlagArray(firstCheckItems);
    })
  ) // <<<<----- Returns Observable<(Observable<boolean>)[]>
  .subscribe(secondCheckObservables => { // <<<<---- Sub 1
    forkJoin(secondCheckObservables)
    .subscribe(booleanArray => { // <<<<---- Sub 2
      this.setSecondFlags(items, booleanArray);
      this.resultingArray = items;
    });
  });
}

private extractNeedsSecondFlagArray(itemsWithFirstFlag: Item[]): Observable<boolean>[] {
  const resultingObservables: Observable<boolean>[] = [];
  itemsWithFirstFlag.forEach(item => {
    if (!item.check2) {
      resultingObservables.push(of(true));
    } else {
      resultingObservables.push(
        this.checkingService.secondCheck(item).pipe(
          take(1),
          catchError(error => {
            const secondFlag = true;
            return of(secondFlag);
          })
        )
      );
    }
  });
  return resultingObservables;
}

private setFirstFlags(items: Item[], booleans: boolean[]){
  let result = []
  for (let i=0 ; i < items.length; i++) {
    items[i].firstFlag = booleans[i]
    result.push(items[i])
  }
  return result;
}

private setSecondFlags(items: Item[], booleans: boolean[]){
  let result = []
  for (let i=0 ; i < items.length; i++) {
    items[i].secondFlag = booleans[i]
    result.push(items[i])
  }
  return result;
}

As you can see in this Stackblitz the code is working fine and the result is as expected.

However, I know nesting Observables / Subscriptions is a bad practice. I'd be super grateful if someone could help me out how to RxJS this with some higher order observable preventing this ugliness.

The problem is I have never seen this done with a forkJoin of multliple calls in an array needing to be executed all at once. I have taken this approach, because in the end I would need to get the resulting array in one go on component init, as it contains information on what user can and cant't do in the application.

\$\endgroup\$
10
  • \$\begingroup\$ I’m voting to close this question because it is not interested in a general review, but rather a specific improvement in the use of a specific API. As such it's not in scope for code review, where any and all aspects of the presented code are up for review. For more information, please check the help center. \$\endgroup\$
    – Vogel612
    Aug 29, 2022 at 13:25
  • \$\begingroup\$ @Vogel612 Give a general review then. You are very much welcome to. This is working code, so if it doesn't belong on SO and it doesn't belong here, where does it belong? \$\endgroup\$
    – H3AR7B3A7
    Aug 29, 2022 at 14:33
  • \$\begingroup\$ The problem with that suggestion is that at least some of the points of a general review will be useless to you (unless you really named your observables firstCheckObservables and secondCheckObservables), which is really a bad experience for everyone involved... \$\endgroup\$
    – Vogel612
    Aug 29, 2022 at 15:40
  • 1
    \$\begingroup\$ Honestly the best suggestion I can give you is to take your question as it is to stack overflow, maybe rewrite a little bit to emphasize that you're not looking for a review, but a "fix" to the API-use and then tell everyone there teeling you to come here that that's where you came from :/ It's not a particularly good suggestion but it's the best I can give you. \$\endgroup\$
    – Vogel612
    Aug 29, 2022 at 16:37
  • 1
    \$\begingroup\$ @Vogel612 I have tried posting working code on SO before, that's why I came here. XD \$\endgroup\$
    – H3AR7B3A7
    Aug 29, 2022 at 16:40

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Browse other questions tagged or ask your own question.