Introducing @let in Angular 18.1
While some enthusiastic Angular writers started covering the @let feature as early as May, it officially shipped with Angular 18.1 on July 10th, 2024. This addition lets developers declare a reusable variable directly inside the component's HTML template (often called the HTML-file), and it belongs to Angular's emerging template syntax — the collection of constructs introduced with the "@" prefix.

Here's a look at its syntax:
@let maxLength = 42; // a valid expression (like number | 'string' | JavaScript expression)
The declared @let-variable can be used throughout the template like this:
<input name="from" [(ngModel)]="from" [maxlength]="maxLength" />
Dynamic @let
It's also possible to assign the value of a template reference variable (or what some of us call a template handle, which makes total sense to Austrians since we also call chickens "Hendl" 🐔):
<input #from name="from" [maxlength]="maxLength" />
@let fromValue = from.value;
This means we now need to differentiate between two approaches:
- Local template variables using
@let(see Angular docs) and - Template reference variables using
#handle(see Angular docs)
That distinction is bound to be an interesting topic for our next Essential Workshop. @let's see.
Async @let
This brings us to the truly practical part. Previously, to get the latest emission of an observable (or a subject), developers had to rely on the so-called ngIf hack. While users of the *ngrxLet directive have enjoyed this capability for a long time, and fans of the even more performant rxLet for even longer (okay, I might be exaggerating a bit), those of us sticking with core Angular were stuck with the clumsy *ngIf or, with the new syntax, the equally awkward @if patterns:
@if (flights$ | async; as flights) {
// basic @if hack
@for (flight of flights; track flight.id) {
[...]
}
}
Or, in an even more convoluted way:
@if ({ flights: flights$ | async }; as data) {
// advanced @if hack, will also show the @if block before the 1st flights$ emit
@for (flight of data.flights; track flight.id) {
[...]
} @empty {
No flights.
}
}
With @let, the same scenario simplifies to this:
@let flights = flights$ | async;
@for (flight of flights; track flight.id) {
[...]
} @empty {
No flights.
}
@let's go! That's a big improvement in readability! This change also addresses one of the most popular Angular GitHub issues, which has been open since March 2017.
It's worth noting that @let-variables are read-only and cannot be reassigned. However, they are re-evaluated on every change detection cycle.
Be aware that you can shadow existing symbol names (such as those defined on the component class). Whether that's a wise practice is debatable. You won't see a compiler error or warning unless you try to use the component class member before defining the @let-variable with the same name. In that case, the compiler will flag an error (NG8016: Cannot read @let declaration 'flights' before it has been defined.). In short, a @let-variable with the same name will effectively hide the component class member from the template.
Some of you might have skipped this section, especially if you've adopted basic Signals now that they're out of Developer Preview. But maybe you'll come back to RxJS eventually, because it still offers more power for complex scenarios 😎
In any case, there's more to talk about.
Two new migration schematics
Switching gears to a different topic.
Are you already taking advantage of the excellent migration schematics we've covered in the past?
Previously available schematics
For migrating to the new template control flow syntax, check this previous post.
ng g @angular/core:control-flow
For moving to the app builder, more details are available in the same post.
ng update @angular/cli --name use-application-builder
For converting to standalone components, refer to this guide.
ng g @angular/core:standalone
Migrating standalone component routes to lazy loading
For all standalone components used in your router configuration, it's time to lazy load 'em:

ng g @angular/core:route-lazy-loading
I've run this migration on a few of my own projects, and it substantially improved my lazy loading setup.
The script will even alert you when it's necessary to first migrate components within your NgModules to standalone before they can be lazy loaded (using the previous migration).
Converting constructor-based DI to the functional inject()
The Angular team has recently confirmed that they will continue to support both DI styles for the foreseeable future (thanks to Rainer for raising this question in the PR). That said, I've been a proponent of the new inject function for a while. However, after reading this thread on the Angular GitHub, I gained a new perspective. Following that discussion, I now hold a more neutral position – the key is consistency across your codebase. Both approaches have their merits. Nevertheless, if you're ready to switch to the functional style, there's now an official migration schematic available:
ng g @angular/core:inject-migration
Pro-tip: After executing the migration, run Prettier to clean up the extra blank lines it may leave behind. I'll share my recommended Prettier setup for Angular projects in a future post.
Wrapping up
There are, of course, many more enhancements and fixes in this release, but most of them won't have a significant impact on our day-to-day work. After the major overhauls from versions 15 through 17, it seems we're in for a period of stability. I'm still waiting for Signal inputs, model, and queries to reach production-ready status, but I doubt we'll see that in 18.3 in six weeks. The next big release, 19, is scheduled for November 2024.
In the meantime, I'd like to suggest a few interesting reads:
- This article by Loraine Lawson on Minko Gechev's perspective on frontend frameworks and the merging of Angular and Wiz
- This video featuring Emma Twersky discussing Angular's future roadmap at the Angular Meetup Graz on June 24th, 2024
- Or, for something a bit different, have a look at the Pixel 9 Pro – I've just ordered the best alternative for those who avoid iOS 😏
Workshops
If you're keen on diving deeper into Angular, we run a number of workshops – available in both English and German.
- Accessibility Workshop, next on Sep. 6th ♿
- Performance Workshop, Sep. 2nd to 4th 🚀
- NG Styling Workshop 🎨
This blog post was written by Alexander Thalhammer. Follow me on Linkedin, X or giThub.
