Combine two or more (boolean) observables on single ngIf using async pipe
Create helper observable 'generators' for common AND / OR / ALL logic
At first it's simple enough to put paymentSelected == false && mode == 'addPayment'
but then when you need to add a new condition you have to update the UI in several places.
It's far better to expose an observable that's called showPaymentPanel$
and then it's clear in both the .ts
and template file exactly what it's for : *ngIf="showPaymentPanel$ | async"
. This also makes it easier to test.
However I was ending up with a lot of code like this:
showTokenizedPaymentMethods$ = combineLatest(this.hasTokenizedPaymentMethods$,
this.showAvailablePaymentMethods$).
pipe(map(([ hasTokenizedMethods, showAvailableMethods ]) =>
showAvailableMethods && hasTokenizedMethods));
And that's a mess! Potentially even worse than multiple async pipes!
So I created helper functions to generate new observables: (globally somewhere)
export const allTrue = (...observables: Array<ObservableInput<boolean>> ) => combineLatest(observables).pipe(map(values => values.every(v => v == true) ), distinctUntilChanged());
export const allFalse = (...observables: Array<ObservableInput<boolean>> ) => combineLatest(observables).pipe(map(values => values.every(v => v == false) ), distinctUntilChanged());
export const anyTrue = (...observables: Array<ObservableInput<boolean>> ) => combineLatest(observables).pipe(map(values => values.find(v => v == true) != undefined ), distinctUntilChanged());
export const anyFalse = (...observables: Array<ObservableInput<boolean>> ) => combineLatest(observables).pipe(map(values => values.find(v => v == false) != undefined), distinctUntilChanged());
Note: These are not operators to be use in a pipe.
In the ts file you create the observable (named to be UI specific) like this:
public showPaymentPanel$ = allTrue(this.hasTokenizedPaymentMethods$, this.showAvailableMethods$);
I will typically create UI observables them even if an existing observable exists:
public showAccountDetails$ = this.isLoggedIn$; // this condition is always subject to change
You can also compose them:
public showSomethingElse$ = allTrue(this.showPaymentPanel$, this.whateverItIs$);
Sometimes I'll expose them to the UI grouped together like this:
public ui = { this.showPaymentPanel$, this.showSomethingElse$ );
Then usage:
`*ngIf="ui.showPaymentPanel$ | async"`
(only ui
should be public so in the template it makes it super clear what you want to allow)
Limit to one pipe as much as possible!
What about using combineLatest
?
For example:
import { combineLatest } from 'rxjs/observable/combineLatest';
import { Observable } from 'rxjs/Observable';
@Component({...})
export class FooComponent {
obs1$: Observable<bolean>;
obs2$: Observable<bolean>;
obs3$: Observable<bolean>;
constructor(){
// set observables
}
get combined$(){
return combineLatest(
this.obs1$,
this.obs2$
this.obs3$,
(one,two,three)=>(one || two) && three);
}
}
// template
<div *ngIf="combined$ | async">
Check the following fiddle for guidance:
https://jsfiddle.net/uehasmb6/11/
More info about the combineLatest
operator here
UPDATE: But if you still want to keep all that logic inside of the template, you could try something like:
<div *ngIf="((myVarA | async) || (myVarB | async)) && ((myVarC | async) !== x)">
But I would advice you against this. Keeping the HTML template as clean as possible is a good practice.