Angular's Template @let Variable: A Debate Worth Having

The unveiling of the @let block in Angular has triggered a lively discussion across the developer community. Opinions are split between those who welcome it as a practical improvement and those who regard it as an avoidable complication.

Why Developers Like @let

Supporters point to the way @let lets you declare variables directly in the template, which tidies up logic, sidesteps problems with falsy values, and generally makes templates easier to scan. It tends to produce more straightforward code, especially when handling asynchronous data and intricate conditions.

The Falsy Value Problem

Before this feature arrived, declaring a template variable typically meant reaching for the ngIf directive in combination with the as keyword. That approach came with a caveat: values that evaluate to falsy—such as 0, an empty string (""), null, undefined, or false—would block the content from rendering. Take this snippet as an illustration:

<div *ngIf="userName$ | async as userName">
  <h1>Welcome, {{ userName }}</h1>
</div>

In a case where userName holds an empty string, the UI would show nothing at all. The @let block offers a way around this limitation:

<div>
  @let userName = (userName$ | async) ?? 'Guest';
  <h1>Welcome, {{ userName }}</h1>
</div>

Handling Complex Templates with Dynamic Columns

There is also a strong use case in templates where column definitions and their corresponding values come from elaborate configurations. Anyone who has built a business application with sizeable tables knows the pain points involved. @let can make the management of such tables noticeably more straightforward.

The Version With @let:

<table mat-table [dataSource]="dataSource">
  @for (columnDef of columnDefs) {
    @let property = columnDef.propertyName;
    <ng-container [matColumnDef]="columnDef.name">
      <th mat-header-cell *matHeaderCellDef>{{ columnDef.header }}</th>
      <td mat-cell *matCellDef="let element">
        @let cellValue = element[property];
        <ng-container *ngIf="columnDef.cellType === 'link'; else plainCell">
          <a [routerLink]="cellValue?.routerLink">{{ cellValue?.value }}</a>
        </ng-container>
        <ng-template #plainCell>{{ cellValue }}</ng-template>
      </td>
    </ng-container>
  }
</table>

The Version Without @let:

<table mat-table [dataSource]="dataSource">
  <ng-container *ngFor="let columnDef of columnDefs">
    <ng-container [matColumnDef]="columnDef.name">
      <th mat-header-cell *matHeaderCellDef>{{ columnDef.header }}</th>
      <td mat-cell *matCellDef="let element">
        <ng-container *ngIf="columnDef.cellType === 'link'; else plainCell">
          <a [routerLink]="element[columnDef.propertyName]?.routerLink">{{ element[columnDef.propertyName]?.value }}</a>
        </ng-container>
        <ng-template #plainCell>{{ element[columnDef.propertyName] }}</ng-template>
      </td>
    </ng-container>
  </ng-container>
</table>

Why Some Developers Are Against @let

On the other side, detractors feel the @let block adds unnecessary layers and invites confusion. Their concerns are laid out below:

  1. Higher Cognitive Load: Each new feature adds another concept to learn. For developers still finding their way around Angular's already expansive toolkit, @let can feel like yet another thing to keep in mind.
  2. Risk of Overuse: There is a real chance developers will lean on @let too liberally, producing templates that are harder to parse and maintain. Imagine several @let blocks layered inside a complex template—clarity tends to suffer. Code reviews will likely need to watch closely to keep unnecessary template variables from creeping in.
  3. Existing Features May Be Enough: Critics argue that current Angular capabilities—particularly the ability to turn observables into signals—already cover most of the same ground. They question whether @let brings enough added value to justify its existence.

A Sample That Points Out the Complexity

Imagine a template that juggles several variables at once. Using @let here might make the code busier and harder to track:

<div>
  @let firstName = user?.firstName;
  @let lastName = user?.lastName;
  @let fullName = `${firstName} ${lastName}`;
  <p>{{ fullName }}</p>
  @if (user?.address) {
    @let street = user.address.street;
    @let city = user.address.city;
    <p>{{ street }}, {{ city }}</p>
  }
</div>

This snippet demonstrates the possible convenience of @let, but it also reveals how a handful of @let declarations can clutter a template quite quickly.

Where I Stand

I would place myself somewhere in the middle. Initially, I struggled to find a compelling use case for this feature—especially since I can already derive signals from observables and use them both in templates and inside my .ts files as needed. That approach centralizes where my values come from and enables reuse without further ceremony. When refactoring, if I run into an *ngIf with the as syntax applied to an observable, I lean toward a signal rather than creating a template variable with @let. Still, the table example (credits to @skorupka_k for raising it) made me reconsider: complex tables are a prime fit for this feature, and I would gladly use @let there in future work. On the whole, I would rather avoid reaching for it, and I would stay vigilant during code reviews to make sure it is not applied without reason. That said, I am glad Angular now offers the option than not having it at all.

Final Thoughts

The @let block remains a polarizing addition to Angular—praised by some as a significant improvement and written off by others as a redundant one. Whether it earns the label "hot" or "not" will largely come down to how individual developers decide to incorporate it into their daily practice.

What is your take? Is @let a welcome enhancement, or does it overcomplicate things? Feel free to share your perspective in the comments.

Source: https://justangular.com/blog/template-local-variables-with-let-in-angular