Introduction
In this blog post, I want to describe the let syntax variable that Angular 18.1.0 will release. This feature has debates in the Angular community because some people like it, others have concerns, and most people don't know when to use it in an Angular application.
I don't know either, but I use the syntax when it makes the template clean and readable.
Bootstrap Application
// app.routes.ts
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: 'let-syntax',
loadComponent: () => import('./app-let-syntax.component'),
title: 'Let Syntax',
},
{
path: 'before-let-syntax',
loadComponent: () => import('./app-no-let-syntax.component'),
title: 'Before Let Syntax',
},
{
path: '',
pathMatch: 'full',
redirectTo: 'let-syntax'
},
{
path: '**',
redirectTo: 'let-syntax'
}
];
// app.config.ts
import { provideHttpClient } from "@angular/common/http";
import { provideRouter } from "@angular/router";
import { routes } from "./app.routes";
import { provideExperimentalZonelessChangeDetection } from "@angular/core";
export const appConfig = {
providers: [
provideHttpClient(),
provideRouter(routes),
provideExperimentalZonelessChangeDetection()
],
}
// main.ts
import { appConfig } from './app.config';
bootstrapApplication(App, appConfig);
Bootstrap the component and the application configuration to start the Angular application. The application configuration provides a HttpClient feature to make requests to the server to retrieve a collection of products, a Router feature to lazy load standalone components, and experimental zoneless.
The first route, let-syntax
, lazy loads AppLetSyntaxComponent
that uses the let syntax. The second route, before-let-syntax
, lazy loads AppNoLetSyntaxComponent
that does not use the let syntax. Then, I can compare the differences in the templates and explain why the let syntax is good in some cases.
Create a Product Service
I created a service that requests the backend to retrieve a collection of products. Then the standalone components can reuse this service.
// app.service.ts
import { HttpClient } from "@angular/common/http";
import { Injectable, inject } from "@angular/core";
const URL = 'https://fakestoreapi.com/products';
type Product = {
id: number;
title: string;
description: string;
category: string;
image: string;
rating: {
rate: number;
}
}
@Injectable({
providedIn: 'root',
})
export class AppService {
http = inject(HttpClient);
products$ = this.http.get<Product[]>(URL);
}
Create the main component
The main component is a simple component that routes to either AppLetSyntaxComponent
or AppNoLetSyntaxComponent
to display a list of products.
// main.ts
import { ChangeDetectionStrategy, Component, VERSION } from '@angular/core';
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
import { appConfig } from './app.config';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, RouterLink, RouterLinkActive],
template: `
<header>Angular {{version}}</header>
<h1>Hello @let demo</h1>
<ul>
<li>
<a routerLink="let-syntax" routerLinkActive="active-link">Let syntax component</a>
</li>
<li>
<a routerLink="before-let-syntax" routerLinkActive="active-link">No let syntax component</a>
</li>
</ul>
<router-outlet />
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class App {
version = VERSION.full;
}
The unordered list displays two hyperlinks for users to click. When a user clicks the first link, Angular loads and renders AppLetSyntaxComponent
. When a user clicks the second link, AppNoLetSyntaxComponent
is rendered instead.
Create a component that uses the syntax
This simple component uses the let syntax in the template to make it clean and readable.
// app-let-syntax.component.ts
import { AsyncPipe } from "@angular/common";
import { ChangeDetectionStrategy, Component, inject } from "@angular/core";
import { AppService } from "./app.service";
@Component({
selector: 'app-let-syntax',
standalone: true,
imports: [AsyncPipe],
template: `
<div>
@let products = (products$ | async) ?? [];
@for (product of products; track product.id; let odd = $odd) {
@let rate = product.rating.rate;
@let isPopular = rate >= 4;
@let borderStyle = product.category === "jewelery" ? "2px solid red": "";
@let bgColor = odd ? "#f5f5f5" : "goldenrod";
<div style="padding: 0.75rem;" [style.backgroundColor]="bgColor">
<div [style.border]="borderStyle" class="image-container">
<img [src]="product.image" />
</div>
@if (isPopular) {
<p class="popular">*** Popular ***</p>
}
<p>Id: {{ product.id }}</p>
<p>Title: {{ product.title }}</p>
<p>Description: {{ product.description }}</p>
<p>Rate: {{ rate }}</p>
</div>
<hr>
}
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export default class AppLetSyntaxComponent {
service = inject(AppService);
products$ = this.service.products$;
}
The component injects AppService
, makes a GET request to retrieve products, and assigns to products$
Observable.
The demo applies the let syntax to the HTML template.
@let products = (products$ | async) ?? [];
The AsyncPipe resolves the products$ Observable or default to an empty array
@let rate = product.rating.rate;
@let isPopular = rate >= 4;
I assigned the product rate to the rate variable and derived the isPopular value. It is true when the rate is at least 4, otherwise, it is false.
@if (isPopular) {
<p class="popular">*** Popular ***</p>
}
<p>Rate: {{ rate }}</p>
isPopular
is checked to display Popular paragraph element and rate is displayed along with other product information.
@let borderStyle = product.category === "jewelery" ? "2px solid red": "";
@let bgColor = odd ? "#f5f5f5" : "goldenrod";
<div style="padding: 0.75rem;" [style.backgroundColor]="bgColor">
<div [style.border]="borderStyle" class="image-container">
<img [src]="product.image" />
</div>
....
</div>
When the product's category is jewelry, the border style is red and 2 pixels wide, and it is assigned to the borderStyle
variable. When array elements have odd indexes, the background color is "#f5f5f5". When the index is even, the background color is goldenrod, which is assigned to the bgColor
variable.
The variables are assigned to style attributes, backgroundColor and border, respectively.
Let's repeat the same exercise without the let syntax.
Create the same component before the let syntax exists
// ./app-no-let-syntax.componen.ts
import { AsyncPipe } from "@angular/common";
import { ChangeDetectionStrategy, Component, inject } from "@angular/core";
import { AppService } from "./app.service";
@Component({
selector: 'app-before-let-syntax',
standalone: true,
imports: [AsyncPipe],
template: `
<div>
@if (products$ | async; as products) {
@if (products) {
@for (product of products; track product.id; let odd = $odd) {
<div style="padding: 0.75rem;" [style.backgroundColor]="odd ? '#f5f5f5' : 'yellow'">
<div [style.border]="product.category === 'jewelery' ? '2px solid green': ''" class="image-container">
<img [src]="product.image" />
</div>
@if (product.rating.rate > 4) {
<p class="popular">*** Popular ***</p>
}
<p>Id: {{ product.id }}</p>
<p>Title: {{ product.title }}</p>
<p>Description: {{ product.description }}</p>
<p>Rate: {{ product.rating.rate }}</p>
</div>
<hr>
}
}
}
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export default class AppNoLetSyntaxComponent {
service = inject(AppService);
products$ = this.service.products$;
}
Let me list the differences
@if (products$ | async; as products) {
@if (products) {
....
}
}
I used nested ifs to resolve the Observable and test the products array before iterating it to display the data.
@if (product.rating.rate > 4) {
<p class="popular">*** Popular ***</p>
}
<p>Rate: {{ product.rating.rate }}</p>
product.rating.rate
occurs twice in the HTML template.
[style.backgroundColor]="odd ? '#f5f5f5' : 'yellow'"
[style.border]="product.category === 'jewelery' ? '2px solid green': ''"
The style attributes are inline and not easy to read inside the tags.
I advise keeping the let syntax to a minimum in an HTML template. Some good use cases are:
- style attributes
- class enablement. Enable or disable a class by a boolean value
- resolve Observable by AsyncPipe
- extract duplicated
The following Stackblitz repo displays the final results:
This is the end of the blog post that introduce the preview feature, let syntax in Angular 18. I hope you like the content and continue to follow my learning experience in Angular, NestJS, GenerativeAI, and other technologies.
Resources:
Stackblitz Demo: https://stackblitz.com/edit/angular-let-demo-uxsvu6?file=src%2Fmain.ts