11 Jul 2024
3 min

Angular: Template @let Variable, Hot or Not?

The recent annoncement of the @let block in Angular has sparked significant debate within the developer community. While some view it as a valuable addition, others see it as an unnecessary complication.

The Case for @let

Proponents argue that @let simplifies template logic by allowing in-template variable declarations, avoiding issues with falsy values, and enhancing readability. It enables more straightforward and cleaner code, particularly when dealing with complex conditions and asynchronous data. The Problem with Falsy Values Previously, to declare a variable in an Angular template, developers often used the ngIf directive with the as keyword. However, this method had a limitation: falsy values like 0, empty strings (“”), null, undefined, and false would prevent content rendering. For example:

<div *ngIf="userName$ | async as userName">
  <h1>Welcome, {{ userName }}</h1>
</div>
If userName were an empty string, nothing would display. The @let block addresses this issue:
<div>
  @let userName = (userName$ | async) ?? 'Guest';
  <h1>Welcome, {{ userName }}</h1>
</div>

Complex Templates with Dynamic Columns

Another good use case for this feature is complex templates where the column definitions and values are derived from complex configurations. Anyone who has worked on a business application with large tables knows the challenges. @let can significantly simplify managing these tables.

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>

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>

The Case Against @let:

Critics argue that the @let block introduces unnecessary complexity and can lead to confusion. Here are some examples illustrating their concerns:
  1. Increased Cognitive Load: Introducing @let adds another concept for developers to learn and understand. This can increase the cognitive load, especially for new developers who are already trying to grasp Angular’s extensive features.
  2. Potential for Misuse: There is a risk of developers overusing or misusing @let , leading to templates that are harder to read and maintain. For example, nesting multiple @let blocks within complex templates can make the code less clear. During code reviews, extra attention will be needed to ensure unnecessary template variables are not being created.
  3. Existing Solutions Are Sufficient: Critics believe that existing Angular features, especially with the ability to convert observables to signals, are sufficient for handling most use cases. They argue that introducing @let
    does not provide significant advantages over these existing solutions.

Example Illustrating Complexity

Consider a scenario where multiple variables are declared within a template. Using @let can make the template more cluttered and harder to follow:
<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>
While this example shows the potential convenience of @let, it also illustrates how multiple @let declarations can lead to a messy template.

My Opinion

I am probably somewhere in the middle. At the very beginning, I did not see many use cases for this feature, especially now when I can create signals from observables and use them not only in my template but also in my .ts files whenever needed. This centralizes the source of my values, allowing for reuse without added complexity. When refactoring my code, if I encounter a case with *ngIf and the “as” syntax with an observable, I would prefer using a signal instead of creating a template variable with @let. However, the example with the table (thanks
@skorupka_k for bringing it up) convinced me that complex tables are a very good place to use this feature. Whenever I work with them again, I would definitely consider using @let. Overall, I would rather avoid using it and would exercise extra caution during code reviews to ensure it is only used when necessary, but still, it’s better to have in Angular than not!
 

Conclusion

The @let
block in Angular is a divisive feature, hailed by some as a game-changer and dismissed by others as redundant. Whether it’s hot or not ultimately depends on how developers choose to integrate it into their workflows.
What are your thoughts? Is the @let feature a welcome addition or an unnecessary complication? Share your views in the comments below.
Share this post

Sign up for our newsletter

Stay up-to-date with the trends and be a part of a thriving community.