How to handle boolean observables with the async pipe in Angular
Asked Answered
E

4

32

I have the following code:

public obs$: Observable<boolean>
<div *ngIf="(obs$ | async) === true || (obs$ | async) === false">
  {{ (obs$ | async) ? 'yes' : 'no' }}
</div>

It works as intended, but the if looks a little verbose.

The problem is that I cannot simply do <div *ngIf="(obs$ | async)">. If I try that, it will work in the case when the observable did not emit a value yet or if the value is true, but it will not work if the value is false, because the if will evaluate to false and the div is not displayed.

I assume the same issue applies if a falsy value is returned, such as an empty string or 0.

Is there a better, easier way of doing that?

Eclectic answered 22/3, 2019 at 9:46 Comment(5)
You can do (obs$ | async) !== nullSedgewake
Can you please shed some light on when the Observable will not emit a value?Subtropics
@SachinGupta Oh wow, it is actually that simple. I tried to compare it to undefined which didn't work and I just gave up. Thanks a lot! If you post an answer I will accept it.Eclectic
@Subtropics I'm making an API call to get the value, so it sometimes takes a few seconds to emit a value.Eclectic
You can also pipe the response and return a false in the catchError operator. That way you'd always be certain that a boolean value is emitted. And you won't have to use an *ngIf on the divSubtropics
S
18

You can do (obs$ | async) !== null

Sedgewake answered 22/3, 2019 at 9:55 Comment(2)
You can't get value of boolean content in Observable with this solution, right?Bricklayer
Its a conditional check to be put in HTML, you wont get the value.Sedgewake
J
60

Here's a way where you can share the subscription and not have to make any custom pipe:

<ng-container *ngIf="{value: obs$ | async} as context">
    {{context.value}}
</ng-container>

The *ngIf will evaluate to true no matter what (I believe) and then you can do your own tests on the value in the context object.

...would be better if they would just implement *ngLet I think!

The thing I like about this solution is that the resulting value is reusable with only the single use of an async pipe and its still pretty concise (even if it isn't perfect).

Jenks answered 30/4, 2019 at 22:52 Comment(8)
To add some clarity the context object is always defined so the ng-container will always show. To check if the Observable has completed use context.value !== null ... thanks, good answerLisandralisbeth
apart from the typo 'asyc' should be 'async' this solved my issue - thanksFrisky
Thanks for noticing that - should be fixed.Jenks
Is this still the way to go? Its working well, but feels hacky...Eugenieeugenio
@Eugenieeugenio you could try this: npmjs.com/package/ng-letJenks
@Jenks thanks, i know the ng-let package, but im confused why something like this isnt backed into angular itself.Eugenieeugenio
@Eugenieeugenio you’re not kidding. Seems like a trivial thing to add and clearly in need.Jenks
Almost 2024 and Angular does not provide an easy way to do this nativelySchmid
S
18

You can do (obs$ | async) !== null

Sedgewake answered 22/3, 2019 at 9:55 Comment(2)
You can't get value of boolean content in Observable with this solution, right?Bricklayer
Its a conditional check to be put in HTML, you wont get the value.Sedgewake
W
1

You can use "!!" to evalulate to boolean. for example *ngIf = "!!(item$ | async)"

Wadsworth answered 20/10, 2022 at 14:6 Comment(1)
simple and it worksSarette
H
0

I think the most elegant way should be creating a custom BoolPipe , and chain it after the async pipe。

@Pipe({name: 'myBool'})
export class MyBoolPipe implements PipeTransform {
  transform(value: boolean, exponent: string): string {
    return !value ? 'yes' : 'no';
  }
}

and init the obj$ in the constructor of your component or service (don't need null check in your HTML template), then chain it with async pipe like this:

<div>{{ obs$ | async | myBool }}</div>
Hexarchy answered 22/3, 2019 at 9:47 Comment(1)
nothing elegant about that, you are just bloating up the codeReindeer

© 2022 - 2024 — McMap. All rights reserved.