I am trying to create a method to return a setting that could be stored in one of three places. I have created three observables, each to attempt to retrieve the setting value from a source. I would like to be able to "join" these in such a way to create an IF/ELSE IF/ELSE construct so that only one of the Observables emits the setting value in the end. The condition to move away from one observable to the next is that the previous "failed", i.e., emitted a value that did not pass a condition. The order in which each option is tried is important.
So my method looks something like this so far:
getSetting(settingName: string): Observable<any> {
const optionOne$ = this.getItemAtFirst(settingName).pipe(
take(1),
filter(setting => setting && this.isSettingValid(settingName, setting)));
const optionTwo$ = this.getItemAtSecond().pipe(
take(1),
filter(setting => setting && this.isSettingValid(settingName, setting)));
const optionThree$ = of(this.defaultSettingValue);
return merge(optionOne$, optionTwo$, optionThree$);
}
This obviously does not work because merge does not provide the selection effect that I need.
Any suggestions? Thanks in advance!
EDIT: The TL;DR
Replace the merge with concat and add first to a pipe on your concat:
concat(optionOne$, optionTwo$, optionThree$)
.pipe(
first()
);
concat subscribes sequentially to each observable, meaning optionOne$ is subscribed to first, it emits its values, when it completes it is unsubscribed from, then concat subscribes to optionTwo$, and so on...until all the observables complete, and then finally emits all the values as an array. Adding first simply cuts this process short since it completes the concat after the first value emitted.
You really need to check that all the observables inside the concat will complete, or you could end up with a condition where one of them never emits or completes, and that will mean you will never get a value emitted from concat.
—-
Assuming that your isSettingValid() returns false if it fails validation, then just add a pipe() with first() to the concat(). See slightly modified version below, and stackblitz here.
This article is also informative. And I suggest this article which has cool gifs for explaining the operators and how they work - I always come back to it when I'm trying to figure out something like this.
I think this answers your question because it emits the first value which passes the validation and ignores the rest.
import { of, Observable, concat } from 'rxjs';
import { take, filter, first } from 'rxjs/operators';
function isSettingValid(setting): boolean {
return setting !== 'error' ? true : false;
}
function getSetting(): Observable<any> {
const optionOne$ = of('error')
.pipe(
take(1),
filter(setting => isSettingValid(setting))
);
const optionTwo$ = of('second')
.pipe(
take(1),
filter(setting => isSettingValid(setting))
);
const optionThree$ = of('default').pipe(
take(1),
filter(setting => isSettingValid(setting))
);
return concat(optionOne$, optionTwo$, optionThree$)
.pipe(
first() // emits only the first value which passes validation then completes
);
}
getSetting()
.subscribe((setting) => {
console.log(setting);
})
I also suggest removing the setting &&, like in my snippet, and putting the null check inside your validation function - it just makes the filter easier to read if you do it this way imo. To make it more concise you could consider replacing the take(1) and filter() with first and a predicate function that will have the same result:
const optionOne$ = of('error')
.pipe(
first(setting => isSettingValid(setting))
);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With