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.
firstCheckObservables
andsecondCheckObservables
), which is really a bad experience for everyone involved... \$\endgroup\$