Preston Lamb | ng-conf | Sep 2020
In the last year or so, I’ve been fully embracing reactive programming in my Angular apps. That means goodbye subscribe
and hello async
pipe! It’s been great, but there have been a couple times where the async
pipe, in conjunction with the *ngIf
structural directive, have not worked as I needed them to. The issue arises when the emitted value from the observable is falsy. When the emitted value is falsy, the *ngIf
doesn’t output the content on the screen. I understand why that happens, but many times the falsy value is a valid result and should be displayed. In this post, we’ll cover an easy way to still use the async
pipe and *ngIf
while still outputting falsy values.
The Issue Explained
Recently, I was working on an application at work where we needed to output the emitted value of an observable. The value was a number with zero being a valid result. We also wanted to display a loading spinner while waiting on data from the API. We did that with the *ngIf
structural directive and the else
option that goes along with it. Here’s a brief sample of what that looked like:
<div
*ngIf=”observableValue$ | async as value; else backupLoader”>
{{ value }}
</div>
<ng-template #backupLoader>
<app-loader></app-loader>
</ng-template>
This worked in most situations. Before the observable emitted the response from the API, the loader would show up on the screen. As soon as the value came back, it replaced the loader. The only time it didn’t work was when the emitted value was 0. The loader would stay on the screen and 0 would never show up. The reason for that is that 0 is a falsy value, and thus the *ngIf
never stopped showing the loader.
One solution to get around this was to convert the returned value to a string and not a number. I didn’t want to have to alter the value though to get my desired result. I turned to Twitter for a little bit of help and came up with the following solution.
Wrapping Observable Values in Objects
The easiest way to solve this issue is to turn the emitted value from the observable into an attribute on an object. You can do this in the component class file, or directly in the HTML. I’m going to do this directly in the HTML. Here’s what that looks like:
<ng-container
*ngIf=”{ observableValue: observableValue$ | async } as data”>
<div>{{ data.observableValue }}</div>
</ng-container>
What we’ve done here is subscribed to the observable with the async
pipe, and put the value into the observableValue
attribute of an object. Notice the curly brackets inside the *ngIf
structural directive. We then use the as
syntax, which renames the variable for use in the template. Inside the double curly brackets, the result is output by accessing the observableValue
attribute of the data
object.
The *ngIf
directive on the ng-container
element will now always evaluate to true
because we have created that object. So, no matter what value is emitted from the observable, we will output it to the screen.
What About the Loader?
Wrapping the observable in an object was nice, and now we can output falsy values. But if we stop with the example in the previous section we’ll have lost our loader element. So the last part to figure out is how to combine wrapping the observable in an object with showing the loader element before the data is loaded. We can do that by using a second *ngif
inside the ng-container
, like this:
<ng-container
*ngIf=”{ observableValue: observableValue$ | async } as data”>
<div *ngIf=”
data.observableValue !== null &&
data.observableValue !== undefined;
else backupLoader”
>
{{ data.observableValue }}
</div>
<ng-template #backupLoader>
<app-loader></app-loader>
</ng-template>
</ng-container>
The ng-container
is the same here as it was before. But the div
that wraps the data.observableValue
output now has a new *ngIf
placed on it. The two checks added here are that the observableValue
attribute value is not null
or undefined
. If the value is null or undefined, then the loader is shown. Otherwise, the value is output (including if it’s falsy).
Conclusion
I have been working with Angular for a long time at this point, and am really bummed that I hadn’t figured this out years ago. This is a great way to output falsy values while using the async
pipe and the *ngIf
structural directive. I will be using this a lot going forward, and hope that this will be useful for you as well.
I also want to give credit to Maxime Robert for writing this great article and to Aaron Frost for the suggestion on Twitter.
ng-conf: The Musical is coming
ng-conf: The Musical is a two-day conference from the ng-conf folks coming on April 22nd & 23rd, 2021. Check it out at ng-conf.org
Top comments (0)