@let Syntax
Introduction
Section titled “Introduction”@let (Angular 18+) lets you declare local variables directly in your templates — similar to a const in TypeScript, but scoped to the template block.
Why do we need this?
Section titled “Why do we need this?”Before @let, reusing a computed value or an async pipe result in templates required awkward workarounds like *ngIf="expr$ | async as result" or nesting <ng-container> elements. @let gives you a clean, readable way to define template-scoped variables.
Real-world analogy
Section titled “Real-world analogy”Think of @let like a sticky note you put on your monitor — a one-time calculation result that you want to reference multiple times without re-calculating or re-subscribing every time.
@let Flow
Section titled “@let Flow”flowchart LR A["{{ user.name }}{{ user.email }}{{ user.role }}"] -->|"repeated reads"| B["❌ Multiple asyncor complex calls"]
C["@let u = user$ | async;{{ u.name }}{{ u.email }}{{ u.role }}"] -->|"one read, reused"| D["✅ Clean & efficient"]
style C fill:#059669,color:#fff style D fill:#059669,color:#fff style A fill:#dc2626,color:#fff style B fill:#dc2626,color:#fffBasic Syntax
Section titled “Basic Syntax”@let user = currentUser$ | async;
@if (user) { <h1>Welcome, {{ user.name }}!</h1> <p>Email: {{ user.email }}</p> <p>Role: {{ user.role }}</p>}Use with async pipe
Section titled “Use with async pipe”The most common use case — subscribe to an observable once and reuse the result:
@let products = productService.products$ | async;@let total = products?.reduce((sum, p) => sum + p.price, 0) ?? 0;
@if (products && products.length) { <h2>Cart ({{ products.length }} items)</h2> <p>Total: {{ total | currency }}</p> @for (product of products; track product.id) { <div>{{ product.name }} — {{ product.price | currency }}</div> }} @else { <p>Your cart is empty</p>}Combine with @if / @for
Section titled “Combine with @if / @for”@let isAdmin = user.roles.includes('admin');@let canEdit = isAdmin || user.permissions.includes('edit');
@if (canEdit) { <button (click)="edit()">Edit</button>}@if (isAdmin) { <button (click)="delete()">Delete</button>}Scoping Rules
Section titled “Scoping Rules”<!-- @let is block-scoped — accessible only within its block -->@let x = 10;
@if (condition) { @let y = x * 2; <!-- ✅ can access parent scopes --> {{ y }}}
<!-- {{ y }} --> <!-- ❌ y is not available outside the if block -->
<!-- @for provides implicit variables -->@for (item of items; track item.id; let i = $index) { @let isFirst = i === 0; <div [class.first]="isFirst">{{ item.name }}</div>}Best Practices
Section titled “Best Practices”- Use
@letwithasyncpipe to avoid multiple subscriptions to the same observable - Keep
@letdeclarations close to where they’re used (near the top of the block) - Prefer
@letover*ngIf="obs$ | async as var"for clarity and type safety - Use descriptive names:
@let formattedDate,@let userProfile, etc. @letvariables are read-only (likeconst) — don’t try to reassign them
Common Mistakes
Section titled “Common Mistakes”- Trying to reassign a
@letvariable (it’s read-only — use a signal instead) - Declaring
@letoutside a template block where it won’t be accessible - Using
@letfor state that changes over time (usesignal()in the component instead) - Confusing
@letwith@for’s implicitletsyntax — they’re different features
Interview Questions
Section titled “Interview Questions”- What is
@letand what problem does it solve in Angular templates? - How does
@letimprove readability compared to*ngIf="obs$ | async as var"? - What is the scoping behavior of
@let— can it be accessed outside its block? - Can you reassign a
@letvariable? - When would you choose
@letovercomputed()in the component class?
Summary
Section titled “Summary”@let provides clean, scoped template variable declarations — eliminating the need for as workarounds and ng-container nesting. Use it to store async pipe results and computed expressions.