Introduction
With the arrival of Angular 18.1, this version introduces an exciting new feature to the compiler: the ability to declare one or more template variables.
How is this feature used, and what are the different use cases?
This article aims to answer.
The compiler’s latest feature: @let
With the latest versions of Angular, the team has introduced new functionality into the compiler, and this functionality translates into the @-syntax.
This is how the new control flow syntax came into being
and, more recently, @let
As a general rule, the easiest way to create a template variable was to use the
- the *ngIf structural directive with the keyword as keyword
or using the new control flow syntax
- @if with the keyword as
<!-- older control flow syntax -->
<div *ngIf="user$ |async as user"> {{ user.name }}
</div> <!-- new control flow syntax -->
@if(user$ |async; as user){ <div>{{ user.name }}</div>
}
This handy feature allowed us to store the result of the async pipe in a variable for use later in the template.
However, this syntax raises a few questions. Here, the condition checks whether the return of the async pipe is true, and therefore whether the return value is different from any value considered false in javascript.
This condition will work really well if the return is an object or an array.
but if the return is a number and particularly the number 0
@if(((numbers$ |async) !=== undefined || (numbers$ |async) !=== null) ; as myNumber){ <div>{{ myNumber }}</div>
}
This is where @let comes in.
@let doesn’t check a condition, it just allows you to declare a local template variable in a simple way
so the code example above becomes much simpler and more elegant to write
@let myNumber = (numbers$ | async) ?? 0;
<div>{{ myNumber }}</div>
This way the myNumber variable will always be displayed.
The different ways of using @let
One of the most classic scenarios with variable declaration is to store the result of a complex expression.
It has always been inadvisable to use a function in a condition. The use of a function in a condition had an impact on performance in the sense that at the slightest mouse movement, or change in the template, the function was re-evaluated.
@let, as described above, does not evaluate, but simply declares a local variable. This variable will be reevaluated only if one of its dependencies changes. So calling a function is not a bad idea for expressions such as complex expression.
<ul> @for(user of users(); track user.id) { @let isAdmin = checkIfAdmin(user); <li>User is admin: {{ isAdmin }}</li> }
</ul>
Use the @let with signals
@let is compatible with signals, and is used as follows
@let userColor = user().preferences?.colors?.primaryColor || 'white';
<span>user favorite color is {{ userColor }}</span>
@let and javascript expression
As you can see, @let can be used to evaluate any kind of javascript expression, apart from, for example, the instantiation of a class
In this way, arithmetic operators are interpreted and several variables can be declared on several different lines or just on one line.
<div> @for (score of scores(); track $index) { @let total = total + score, max = calcMax(score); <h1>final score: {{ total }}</h1> }
</div>
Other cool things that @let brings
As described above, the behaviour of @let is very similar to the behaviour of let in javascript, which has the following benefits
- the scope works like the let scope in javascript
- better typing interference in the template
- an error is raised if a variable (let) is used before being declared