Built-in Control Flow (v17)

📚 What is Built-in Control Flow?

Angular 17 introduced new template syntax replacing structural directives:

  • @if / @else: Replaces *ngIf
  • @for with track: Replaces *ngFor (track is required!)
  • @switch / @case / @default: Replaces *ngSwitch
  • @empty: Shows content when @for array is empty
  • Better performance: Optimized change detection
  • Better type narrowing: TypeScript understands the types better

🎯 Interview Questions

  • Q1: Why is track required in @for?
  • A: Track identifies items for efficient DOM updates. Without it, Angular can't optimize re-renders. Use unique identifier.
  • Q2: What are the advantages of new control flow over *ngIf/*ngFor?
  • A: Better performance, cleaner syntax, built-in (no imports needed), better type narrowing, required track prevents bugs.
  • Q3: What implicit variables are available in @for?
  • A: $index, $first, $last, $even, $odd, $count
  • Q4: Can you still use *ngIf and *ngFor?
  • A: Yes, they still work but new syntax is recommended. You need to import CommonModule/NgIf/NgFor for old syntax.

🔧 @if / @else Demo

✅ Welcome back!

You are a regular user.

🔧 @for with track Demo

1. Learn Angular signals First
2. Master control flow
3. Build demo app Last

Total: 3 | Completed: 1

🔧 @switch / @case Demo

🟡 Medium Priority - Complete this week

📋 Old vs New Syntax

❌ Old (*ngIf, *ngFor)

<div *ngIf="isLoggedIn; else loggedOut">
  Welcome!
</div>
<ng-template #loggedOut>
  Please log in
</ng-template>

<li *ngFor="let item of items; 
    let i = index;
    trackBy: trackById">
  {{ i }}: {{ item.name }}
</li>

<div [ngSwitch]="status">
  <p *ngSwitchCase="'active'">Active</p>
  <p *ngSwitchDefault>Unknown</p>
</div>

✅ New (@if, @for)

@if (isLoggedIn) {
  <div>Welcome!</div>
} @else {
  <div>Please log in</div>
}

@for (item of items; track item.id;
     let i = $index) {
  <li>{{ i }}: {{ item.name }}</li>
} @empty {
  <li>No items</li>
}

@switch (status) {
  @case ('active') {
    <p>Active</p>
  }
  @default {
    <p>Unknown</p>
  }
}

💻 Control Flow Code


// ===== @if / @else / @else if =====

@if (user) {
  <p>Hello, {{ user.name }}!</p>
} @else if (loading) {
  <p>Loading...</p>
} @else {
  <p>Please log in</p>
}

// Type narrowing works!
@if (user) {
  {{ user.email }} // TypeScript knows user is defined here
}

// ===== @for with track (REQUIRED!) =====

@for (item of items; track item.id) {
  <div>{{ item.name }}</div>
}

// Available variables:
// $index - zero-based index
// $first - true if first item
// $last - true if last item
// $even - true if even index
// $odd - true if odd index
// $count - total number of items

@for (user of users; track user.id; 
      let idx = $index, 
      let isFirst = $first,
      let isLast = $last) {
  <div [class.highlight]="isFirst">
    {{ idx + 1 }}. {{ user.name }}
    @if (isLast) { (Last one!) }
  </div>
} @empty {
  <p>No users found</p>
}

// track can be a property or expression
@for (item of items; track item.id) { }
@for (item of items; track item) { } // Track by reference
@for (item of items; track $index) { } // Track by index (use carefully)

// ===== @switch / @case / @default =====

@switch (status) {
  @case ('pending') {
    <span class="yellow">Pending...</span>
  }
  @case ('approved') {
    <span class="green">Approved ✓</span>
  }
  @case ('rejected') {
    <span class="red">Rejected ✗</span>
  }
  @default {
    <span>Unknown status</span>
  }
}

// ===== Nesting is clean =====

@for (category of categories; track category.id) {
  <h2>{{ category.name }}</h2>
  @for (item of category.items; track item.id) {
    @if (item.inStock) {
      <p>{{ item.name }} - Available</p>
    }
  } @empty {
    <p>No items in this category</p>
  }
}