# 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