In this post, we are going to cover all the features that we have available for using the Angular ngIf
core directive.
Besides the most commonly used features, we are going to learn how to avoid a potential ngIf
anti-pattern that we might run into while developing more complex UI screens that consume a lot of Observable data coming from different sources (backend, Observable services, stores, etc.).
Table Of Contents
In this post, we will cover the following topics:
- What is Angular ngIf?
- How does ngIf compare to hiding elements using CSS?
- What type of expressions can ngIf accept?
- The ngIf else syntax
- The ngIf if then else syntax
- Consuming observable data with ngIf and the async pipe
- A potential anti-pattern while consuming Observable data with ngIf
- The Single Data Observable pattern
- How does ngIf work under the hood?
- Summary
This post is part of our ongoing series on Angular Core features, you can find all the articles available here.
So without further ado, let's get started learning everything that we need to know about Angular ngIf
!
Note: If instead of ngIf
you are looking for the @if
syntax, then check my other guide instead: Angular @if: Complete Guide
What is Angular ngIf?
Even though HTML is also a programming language in its own right, it does not have an if statement, unlike for example Javascript.
The Angular
ngIf
directive works essentially as an if statement for HTML, adding this missing feature to the language under the form of the specialngIf
attribute.
We need to pass a condition to ngIf
, in order for it to work. Here are a couple of examples:
In this example, the container div will only be shown to the user if the user is logged in, otherwise the whole content of the div is not visible.
Inside the container div, there is a button only visible if the user is an administrator.
What if I don't have anywhere convenient to apply ngIf?
In the case of container elements, if there is no container available for the section that we want to show or hide, then we don't have to create a container div just to be able to apply ngIf
.
Instead, we can apply ngIf
on top of the ng-container
directive. This will show or hide the content of ng-container
, without having to create an extra div just for that:
How does ngIf compare to hiding elements using CSS?
Even though HTML does not have a built-in if statement, there are ways of hiding portions of the page with just plain CSS, by using the display
and
visibility
attributes.
We can easily add or remove these CSS attributes to an HTML element using Javascript and hide an element from the page. But that is not the same as using ngIf
.
With ngIf
, if an element is hidden then that element does not exist at all in the page.
Meaning that if you inspect the page using for example the Chrome Dev Tools, you won't find any HTML element present in the DOM.
Instead of it, you will find a strange-looking HTML comment similar to this one, where the ngIf
directive was applied:
<!--bindings={
"ng-reflect-ng-if": "false"
}-->
This comment is there just for debugging purposes, to help identify where the missing element should have been placed in case it was visible.
This is very different than the behavior of the display
or visibility
CSS properties. For example, if we set the display
property to the value none
, the HTML element will be hidden.
But if we inspect the page with the Dev Tools, we will see that DOM elements are still there present on the page, it's just that they are not visible:
With the CSS visibility
attribute set to hidden
, something very similar would occur. The HTML element would be hidden from the user, but still present on the page upon inspection with the Dev Tools.
With hidden visibility, the element will still occupy some blank space on the page, even though the element is hidden. This is unlike the use of display:none
, where no space on the page is occupied.
But in both cases, with CSS the elements are still present in the DOM, consuming resources however small, unlike with ngIf
where hidden elements simply don't exist.
In general, while building Angular applications, we should always prefer to hide elements using
ngIf
instead of using plain CSS.
What type of expressions can ngIf accept?
The ngIf
directive can take as input any valid Typescript expression and not just a boolean. The thruthiness of the expression is then going to get evaluated, to determine if the element should be shown or not.
Besides booleans we can also pass to ngIf
for example strings, arrays, objects, etc. Here are a few examples of what would happen if we passed other primitive types to ngIf
:
And here are some more examples of passing arrays and objects to ngIf
:
As we can see, all that it matters to determine if an element is shown or not is the truthiness of the expression passed to ngIf
.
The ngIf else syntax
If we could use an "if" syntax in HTML, we will also need an else clause, just like in Javascript we have the if-else statement.
In Angular, we can use the ngIf
else syntax, in the following way:
Besides the courses.length
expression, we can also pass to ngIf
an else clause, that points to a template reference (the noCourses
template in this case).
If the expression is falsy, the noCourses
template will be instantiated and applied to the page, right where the ngIf
directive was applied.
Otherwise, if the expression courses.length
remains truthy, then the noCourses
template will never be instantiated, and won't be present at all on the page.
The ngIf then else syntax
The Angular ngIf
directive also supports a if-then-else syntax, just like we have available in Javascript. Here is an example:
In this example, we have applied ngIf
to an ng-container
directive. Either one of the two templates coursesList
or noCourses
is going to be instantiated, depending on the truthiness of the courses.length
expression.
Unlike Javascript, this syntax does not support multiple "else if" clauses, but you can implement equivalent functionality using ngSwitch
.
Consuming observable data with ngIf and the async pipe
If you are building applications in reactive style, then ngIf
is usually used to feed the observable data to the template.
The ngIf
directive can be combined with the async
pipe in order to consume an Observable in the following way:
In this example, courses$
is an Observable emitting as values arrays of course objects. This Observable could have been fetched from memory, or from an in-memory store.
Independently of the origin of the data, the async
pipe will subscribe to the Observable, and make the values emitted by it available to the template.
In order to apply the async
pipe, we use the ngIf
syntax with the "as" syntax. The values emitted by the Observable are going to be available inside the scope of the ngIf
directive via the courses
local template variable.
One of the advantages of consuming Observables with the async pipe directly in the template is that the pipe will take care of unsubscribing from the Observable automatically when the component gets destroyed.
Another advantage of consuming data this way is that the component view will get automatically updated with the latest Observable data if using OnPush change detection.
A potential anti-pattern while consuming Observables with ngIf
Using this combination of ngIf
and the async
pipe to consume Observable data is very convenient, but this can be easily misused in more complex user interfaces, where multiple sources of Observable data need to be displayed in different sections of the page.
You might have come across some components with templates that look a lot like this:
This page is made up of three different peer sections, at the same level of the HTML tree:
- the header only needs the user
- the body needs all data: courses, the lessons, and the user
- the footer only needs the courses and the lessons
In order to consume the Observable data, we had to resort to repeating the use of ngIf
and the async
pipe, at multiple levels of the page.
We also used nested ng-container
directives in both the body and the footer, just to be able to access multiple Observables in one section of the page.
In the case of the body, we even had to resort to 3 levels of nesting, and we have seen components with sometimes 5 or 6 levels of nesting, just to consume Observable data.
When does this overuse of ngIf/async occur?
This will only happen in more complex components, where the data of each Observable is used a little bit everywhere on the page, and not just in one well-contained section.
Also, the component might begin simple, with each Observable only being used in a certain part of the page, but over time new requirements show up that cause the observable data to be used in new places of the page, and then the nested ng-container
directives start to multiply.
Notice that nesting ng-container
by itself is not problematic, and is normal when we have multiple structural directives like ngIf
, ngFor
, etc. in one section of the page, as we can only apply one structural directive per element.
What we are talking about here, is the repeated use of this ngIf
and async
pipe combination, just for the purpose of accessing data, and nothing more.
Does this cause any problems in practice?
Not only does this code repetition and nesting not look right visually, this makes the component harder to read and maintain over time.
You might think of splitting the component into many different subcomponents to try to alleviate this. The problem is that the different sections of the page might make much more sense to be put on the same component because they are tightly related to each other.
How is this commonly solved?
A common attempt to solve this problem is to try to get all the data needed at once, at the top of the component, like so:
Here we have nested the ngIf
directives at the top of the component template so that we get all the data needed anywhere in the component upfront.
All this nesting doesn't look great visually, but at least it prevents a lot of the code repetition. If it works, why not? The problem is, this doesn't work very well in practice in terms of user experience.
Why doesn't this upfront nesting solution work great in practice?
Usually, we want to show to the user something on the screen as early as possible, and some of these data sources might take longer to fetch the data than others.
For example, imagine that the user$
data comes from an existing in-memory store, and is immediately available.
But the courses$
data, on the other hand, is coming from a REST API call which is very fast, while the lessons$
data comes from another API call, that takes longer.
Ideally, we want to show the user$
data immediately as it's already available in memory, then we can show the courses data once it arrives, and then finally display also the lessons data.
The problem is, all this ngIf
nesting would prevent that! With the current solution based on nesting ngIf
3 times, the component is only going to be displayed to the user when all the data is ready.
So as we can see, this solution is not only visually unappealing, there can also be practical consequences in terms of user experience.
So how can we improve on this, is there an alternative way?
The Single Data Observable pattern
For simpler screens, the issues that we have discussed above are probably not a huge problem.
But if your screen starts growing in complexity, and you start to feel that all this ngIf
/async
nesting is starting to cause maintainability issues and user experience problems, you might want to refactor your component into the single data observable pattern.
With this pattern, here is what the template of the component will look like:
As you can see, there is no more nesting involved, and no more multiple uses of the ngIf
/async
combination. We access all the data that the template needs in one go at the top of the component, via a data$
Observable, so we only apply the async
pipe once.
This Observable contains all the data that the component template will need throughout its lifecycle, which explains the name of the pattern.
How does this pattern help with the previous problems?
Besides avoiding the unnecessary nesting and making the template much easier to read and reason about, this pattern also helps with the UI issues that we mentioned before.
Actually, you can build this data$
Observable to behave in any way that you need to provide the best user experience.
For example, if the user$
data is already available, you can have this Observable immediately emit a value with the user property filled in, and the other properties with default values:
This would allow the user$
data to be displayed, while the rest of the data is still being fetched. Once the courses API call ends, we can then emit a second value for the data$
Observable containing the courses:
This would allow us to display even more data to the user, maybe even already hiding the global loading indicator, and leaving only a more local loading indicator still spinning.
Later, when the lessons arrive from the backend, we can emit one final value of the data$
Observable, containing all the data:
How to create the Single Data Observable?
The data$
Observable can be built with any combination of RxJs operators that you need, in order to meet the exact needs of the UI.
But most commonly, the best way to build this Observable is to use RxJs combineLatest. Here is an example of how to build the data$
Observable:
Let's break down what is going on here, step by step. To start, we are creating three separate Observables (user$
, courses$
and lessons$
).
The first user$
Observable comes from an in-memory store, and does not emit a default value. This makes sense, given that there is no default user profile.
But the two other Observables, coming from backend services, do have logical default values (the empty array), defined via the startWith
operator.
This means that each of those Observables will emit first the empty array []
, and only later they will emit the result of the backend call.
Finally, we have combined all 3 observables using combineLatest
, which will create a result Observable, that emits tuple values.
These tuples contain the values emitted by each of the composing Observables in order, meaning [user, courses, lessons]
. We then transform this tuple into an object of type ExampleData
, using the map operator.
And with this, using just a couple of very commonly used RxJs operators, we have built exactly the data$
Observable that we need, and now our template is a lot simpler and maintainable, at a small expense of a bit of extra code.
In case you are not familiar with combineLatest
If you use this pattern in your applications, notice an important property of
combineLatest
: it will not emit its first tuple until its composing Observables all emit their first value.
This is why adding default values to courses$
and lessons$
is important, otherwise, we wouldn't be able to feed to the template the user and courses data immediately as it becomes available.
The data$
Observable that we have defined will wait for a user to be emitted, and after that, it will continue to emit values whenever any of the user$
,
courses$
and lessons$
Observables emit themselves values.
After combineLatest
emits its first tuple value, then any new values emitted by
user$
, courses$
or lessons$
will result in the emission of a new result tuple.
When is it worth to use the Single Data Observable pattern?
It's probably not worth it to use the single data observable pattern systematically, but only in screens that consume several observable sources, where its hard to tell upfront exactly where in the page the multiple observables are going to be needed over time.
If the screen is going to be complex and accept multiple observables, we might as well use it from the beginning. But if not, its very simple to refactor into this pattern later.
If you are building your UI in reactive style, this pattern will make your larger and more complex components a lot easier to maintain.
On the other hand, this pattern is overkill for simpler screens, so its use is better decided on a case-by-case basis.
How does ngIf work under the hood?
To wrap up our deep dive into the ngIf
directive, let's talk a bit about the strange *
syntax applied to *ngIf
, and what it means.
The *
syntax means that ngIf
is a structural directive, meaning that it affects the structure of the page.
When Angular sees the *
, the template compiler is going to take the template in its initial form:
And Angular is going to de-sugar the *ngIf
syntax into the following form:
As we can see, under the hood the *ngIf
directive is simply a plain Angular attribute that targets the property ngIf
.
The *
syntax simply means that the content of the element where the directive is applied is considered an ng-template
, that might or might not be included in the page depending if the structural directive applied to it decides to instantiate the template or not.
Notice that this under the hood de-sugaring process happens with all structural directives (like *ngFor
, etc.), and not only with ngIf
.
Summary
As we have learned, the ngIf
directive works just like the missing if-then-else feature of the HTML language!
Using it, we can easily add or remove elements from the page, depending on the truthiness of a Javascript expression.
When an element gets removed from the page using ngIf
, it gets removed completely, its not like the element is hidden using CSS.
If you adopt a reactive style for your applications, ngIf
is often used in combination with the async
pipe, to consume observable data.
For components with a lot of data sources, this might lead to lots of ngIf
nesting and code repetition in the template just to access the data, as well as page startup UI issues.
If you run into these issues, consider refactoring your component into using the single data observable pattern, where only one data observable is fed to the view.
This will greatly simplify the template and improve the component startup experience. The example given with combineLatest
is a common one, but feel free to add any other combination of operators in order to get exactly the data observable that the view needs.
I hope that you have enjoyed this post, if you would like to learn a lot more about all the available Angular Core directives, we recommend checking the Angular Core Deep Dive course.
Also, if you have some questions or comments please let me know in the comments below and I will get back to you.
To get notified of upcoming posts on Angular, I invite you to subscribe to our newsletter:
And if you are just getting started learning Angular, have a look at the Angular for Beginners Course: