# Angular Control Flow Syntax in 2026: @if, @for, @switch and Interview Questions
> Master Angular's built-in control flow syntax with @if, @for, and @switch blocks. Learn the track expression, contextual variables, and common interview questions about Angular 22 template syntax.
- Published: 2026-09-03
- Updated: 2026-09-03
- Author: Anthony Fillion-Maillet
- Tags: angular, control-flow, templates, interview
- Reading time: 9 min
---
Angular's control flow syntax (`@if`, `@for`, `@switch`) replaces the structural directives `*ngIf`, `*ngFor`, and `*ngSwitch` that dominated Angular templates for years. Introduced in Angular 17, stable since Angular 18, and the default in Angular 22, this block-based syntax brings templates closer to standard programming constructs while enabling better performance optimizations.
> **Migration available**
>
> Run `ng generate @angular/core:control-flow` to automatically migrate existing templates from structural directives to the new control flow syntax. Both syntaxes coexist during migration, but `ngIf`, `ngFor`, and `ngSwitch` are soft-deprecated since Angular 19.
## Why Angular Replaced Structural Directives with Block Syntax
Structural directives required importing `CommonModule`, used microsyntax that differed from JavaScript, and forced developers to wrap content in `ng-template` for complex conditions. The new control flow syntax solves these problems:
- No imports required: `@if`, `@for`, and `@switch` are built into the framework
- JavaScript-like syntax: conditions and loops read like standard code
- Native `@else` support: no more `ng-template` references for else branches
- Better tree-shaking: unused control flow blocks have zero bundle impact
The [Angular documentation](https://angular.dev/guide/templates/control-flow) recommends fully adopting block syntax in all new projects, and migrating existing codebases incrementally.
## @if: Conditional Rendering Without ng-template
The `@if` block conditionally renders content based on a truthy expression. Unlike `*ngIf`, it supports `@else if` and `@else` branches directly:
```typescript
// user-status.component.ts
import { Component, input } from '@angular/core';
@Component({
selector: 'app-user-status',
template: `
@if (user().role === 'admin') {
} @else if (user().role === 'editor') {
} @else {
}
`
})
export class UserStatusComponent {
user = input.required<{ role: string }>();
}
```
The `@if` block also supports variable aliasing with the `as` keyword, which extracts values from nested expressions:
```html
@if (user().profile?.settings?.theme; as theme) {
Current theme: {{ theme }}
}
```
This pattern is particularly useful when working with async data or deeply nested objects, since the aliased variable is only defined when the condition is truthy.
## @for: Iteration with Mandatory Track Expression
The `@for` block iterates over any JavaScript iterable, with optimizations for arrays. Unlike `*ngFor`, it requires a `track` expression:
```typescript
// product-list.component.ts
import { Component, input } from '@angular/core';
interface Product {
id: string;
name: string;
price: number;
}
@Component({
selector: 'app-product-list',
template: `
@for (product of products(); track product.id) {
} @empty {
No products available
}
`
})
export class ProductListComponent {
products = input.required();
}
```
The `track` expression tells Angular how to identify each item across re-renders. Choosing the right tracking property directly impacts performance:
| Track Expression | Use Case | Performance Impact |
|-----------------|----------|--------------------|
| `track item.id` | Items with unique identifiers | Optimal: minimal DOM updates |
| `track $index` | Static lists that never reorder | Acceptable: full re-render on reorder |
| `track item` | Reference tracking | Poor: new reference = new DOM node |
### Contextual Variables in @for Blocks
Angular provides implicit variables inside `@for` blocks that expose iteration metadata:
```html
@for (item of items(); track item.id; let i = $index, isLast = $last) {
{{ i + 1 }}. {{ item.name }}
}
```
Available contextual variables:
- `$index`: zero-based position
- `$count`: total number of items
- `$first`, `$last`: boolean flags for first/last items
- `$even`, `$odd`: boolean flags based on index parity
## @switch: Type-Safe Conditional Branching
The `@switch` block provides exhaustive pattern matching with strict equality (`===`) comparison:
```typescript
// status-badge.component.ts
import { Component, input } from '@angular/core';
type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered';
@Component({
selector: 'app-status-badge',
template: `
@switch (status()) {
@case ('pending') {
Pending
}
@case ('processing') {
Processing
}
@case ('shipped') {
Shipped
}
@case ('delivered') {
Delivered
}
}
`
})
export class StatusBadgeComponent {
status = input.required();
}
```
Unlike JavaScript's `switch`, Angular's `@switch` has no fallthrough behavior. Multiple conditions targeting the same block require consecutive `@case` statements:
```html
@switch (userRole()) {
@case ('admin')
@case ('superadmin') {
}
@case ('editor')
@case ('reviewer') {
}
@default {
}
}
```
### Exhaustive Type Checking with @default never
Angular 22 supports compile-time exhaustiveness checking. Using `@default never;` declares that no remaining cases should exist:
```typescript
type Theme = 'light' | 'dark' | 'system';
// Compile error if a Theme value is missing from @case blocks
@switch (theme()) {
@case ('light') { /* ... */ }
@case ('dark') { /* ... */ }
@case ('system') { /* ... */ }
@default never;
}
```
Adding a new value to the `Theme` union triggers a compilation error, forcing developers to handle all cases.
## Control Flow Syntax and Angular Signals Integration
Control flow blocks integrate seamlessly with [Angular Signals](/technologies/angular/interview-questions/angular-signals), enabling fine-grained reactivity:
```typescript
// dashboard.component.ts
import { Component, computed, signal } from '@angular/core';
@Component({
selector: 'app-dashboard',
template: `
@if (isLoading()) {
} @else if (hasError()) {
} @else {
@for (item of filteredItems(); track item.id) {
} @empty {
No items match your filters
}
}
`
})
export class DashboardComponent {
items = signal- ([]);
filter = signal('');
isLoading = signal(true);
hasError = signal(false);
errorMessage = signal('');
// Computed signal recalculates only when dependencies change
filteredItems = computed(() =>
this.items().filter(item =>
item.name.toLowerCase().includes(this.filter().toLowerCase())
)
);
}
```
When a signal updates, Angular re-evaluates only the affected control flow blocks. Combined with [Angular's zoneless change detection](/blog/angular/angular-19-zoneless-change-detection-performance), this enables highly optimized rendering.
## Common Interview Questions About Angular Control Flow
Technical interviews frequently test understanding of control flow syntax, especially the differences from structural directives.
### Question 1: Why is track mandatory in @for?
The `track` expression provides Angular with a stable identity for each item. Without it, Angular cannot efficiently determine which DOM nodes to create, update, or destroy when the collection changes. Making `track` mandatory forces developers to make an explicit decision about identity, avoiding the performance pitfalls of implicit reference tracking.
### Question 2: How does @if differ from *ngIf with async pipe?
Both can handle observables, but `@if` with signals provides synchronous access to values:
```typescript
// With *ngIf and async pipe (legacy)
{{ user.name }}
// With @if and signals (modern)
@if (user(); as user) {
{{ user.name }}
}
```
The signal approach avoids subscription management and integrates better with Angular's change detection.
### Question 3: Can @switch replace complex @if/@else if chains?
`@switch` should be used when comparing a single expression against multiple discrete values. For complex boolean conditions involving different expressions, `@if/@else if` remains more appropriate:
```html
@switch (status()) {
@case ('active') { ... }
@case ('inactive') { ... }
}
@if (isAdmin() && hasPermission('write')) {
...
} @else if (isEditor()) {
...
}
```
### Question 4: What happens to @for when the collection is empty?
When the iterable contains no items, Angular skips the `@for` content entirely. The optional `@empty` block renders fallback content in this case. Without `@empty`, nothing renders, which differs from some frameworks that render an empty container.
## Migrating from Structural Directives to Control Flow
The Angular CLI provides an automated migration schematic:
```bash
# Migrate entire project
ng generate @angular/core:control-flow
# Migrate specific directory
ng generate @angular/core:control-flow --path=src/app/features
```
The schematic handles most transformations automatically, but review the output for edge cases:
| Structural Directive | Control Flow Equivalent |
|---------------------|------------------------|
| `*ngIf="condition"` | `@if (condition) { }` |
| `*ngIf="condition; else elseBlock"` | `@if (condition) { } @else { }` |
| `*ngFor="let item of items"` | `@for (item of items; track item) { }` |
| `*ngFor="let item of items; index as i"` | `@for (item of items; track item; let i = $index) { }` |
| `[ngSwitch]` + `*ngSwitchCase` | `@switch` + `@case` |
After migration, remove `CommonModule` imports from standalone components that no longer use structural directives.
## Key Takeaways for Angular Control Flow Syntax
- Angular's control flow syntax (`@if`, `@for`, `@switch`) is the recommended approach since Angular 19, replacing structural directives
- The `track` expression in `@for` is mandatory: use unique identifiers like `item.id` for optimal performance, avoid `track item` which causes reference-based re-renders
- `@if` supports native `@else if` and `@else` branches without `ng-template`, and the `as` keyword aliases truthy values for reuse
- `@switch` uses strict equality and has no fallthrough: consecutive `@case` statements target the same block, and `@default never;` enables exhaustive type checking
- Control flow blocks integrate with [Signals](/technologies/angular/interview-questions/angular-signals) for fine-grained reactivity: only affected blocks re-render when signals update
- Run `ng generate @angular/core:control-flow` to migrate existing templates automatically
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/en/blog/angular/angular-control-flow-syntax-if-for-switch-guide