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>
<div>
@let userName = (userName$ | async) ?? 'Guest';
<h1>Welcome, {{ userName }}</h1>
</div>
Complex Templates with Dynamic Columns
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:
- 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.
- 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.
- 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
<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>
My Opinion
Conclusion
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.