Skip to content
beginner Phase 4 · Bindings, Structural Directives, and Events

Structural Directives: ngIf, ngFor, ngSwitch

Add or remove elements based on conditions and collections.

45m
0 problems
Topic Progress 0%

Classic Structural Directives

Structural directives change the DOM layout by adding or removing elements. The classic ones are *ngIf, *ngFor, and *ngSwitch.

*ngIf — Conditional Rendering

@Component({
  selector: 'app-user-profile',
  template: `
    <div *ngIf="isLoggedIn; else loggedOut">
      <h2>Welcome, {{ user.name }}</h2>
      <button (click)="logout()">Log out</button>
    </div>

    <ng-template #loggedOut>
      <p>Please log in</p>
      <button (click)="login()">Log in</button>
    </ng-template>
  `,
})
export class UserProfileComponent {
  isLoggedIn = true;
  user = { name: 'Alice' };

  login(): void { this.isLoggedIn = true; }
  logout(): void { this.isLoggedIn = false; }
}

*ngFor — List Rendering

@Component({
  selector: 'app-todo-list',
  template: `
    <ul>
      <li *ngFor="let todo of todos; let i = index; let first = first; let last = last; let odd = odd; let even = even"
          [class.first]="first"
          [class.last]="last"
          [class.odd]="odd"
          [class.even]="even">
        {{ i + 1 }}. {{ todo.text }} <span *ngIf="todo.done">(done)</span>
      </li>
    </ul>
  `,
})
export class TodoListComponent {
  todos = [
    { text: 'Learn Angular', done: true },
    { text: 'Build app', done: false },
    { text: 'Deploy', done: false },
  ];
}

*ngFor with trackBy

@Component({
  selector: 'app-user-list',
  template: `
    <div *ngFor="let user of users; trackBy: trackByUserId">
      {{ user.name }}
    </div>
  `,
})
export class UserListComponent {
  users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
  ];

  trackByUserId(index: number, user: any): number {
    return user.id;
  }
}

*ngSwitch — Multiple Branches

@Component({
  selector: 'app-status',
  template: `
    <div [ngSwitch]="status">
      <p *ngSwitchCase="'loading'">Loading...</p>
      <p *ngSwitchCase="'success'">Data loaded!</p>
      <p *ngSwitchCase="'error'">Error occurred</p>
      <p *ngSwitchDefault>Unknown status</p>
    </div>
  `,
})
export class StatusComponent {
  status = 'loading';
}

New Control Flow: @if, @for, @switch

Angular 17+ introduces built-in control flow blocks that replace *ngIf, *ngFor, and *ngSwitch with cleaner syntax and better performance.

@if — Conditional Rendering

@Component({
  selector: 'app-user-profile',
  template: `
    @if (isLoggedIn) {
      <h2>Welcome, {{ user.name }}</h2>
      <button (click)="logout()">Log out</button>
    } @else {
      <p>Please log in</p>
      <button (click)="login()">Log in</button>
    }
  `,
})
export class UserProfileComponent {
  isLoggedIn = true;
  user = { name: 'Alice' };

  login(): void { this.isLoggedIn = true; }
  logout(): void { this.isLoggedIn = false; }
}

@for — List Rendering

@Component({
  selector: 'app-todo-list',
  template: `
    @for (todo of todos; track todo.id; let i = $index, first = $first, last = $last, odd = $odd, even = $even) {
      <li [class.first]="first" [class.last]="last" [class.odd]="odd" [class.even]="even">
        {{ i + 1 }}. {{ todo.text }} @if (todo.done) { <span>(done)</span> }
      </li>
    } @empty {
      <li>No todos yet</li>
    }
  `,
})
export class TodoListComponent {
  todos = [
    { id: 1, text: 'Learn Angular', done: true },
    { id: 2, text: 'Build app', done: false },
  ];
}

@switch — Multiple Branches

@Component({
  selector: 'app-status',
  template: `
    @switch (status) {
      @case ('loading') { <p>Loading...</p> }
      @case ('success') { <p>Data loaded!</p> }
      @case ('error') { <p>Error occurred</p> }
      @default { <p>Unknown status</p> }
    }
  `,
})
export class StatusComponent {
  status = 'loading';
}

Old vs New Syntax

Feature Old (*ngIf) New (@if)
Syntax *ngIf="condition" @if (condition) { }
Else *ngIf="condition; else elseBlock" @else { }
Else-if Multiple *ngIf @else if (condition) { }
Local variable *ngIf="condition as var" @if (condition; as var) { }
Performance Good Better (compiled to efficient JS)
Feature Old (*ngFor) New (@for)
Syntax *ngFor="let item of items" @for (item of items; track item.id)
Index let i = $index $index (built-in)
Empty No built-in @empty { }
TrackBy trackBy: fn track expression (required)

Why the New Control Flow Is Better

  1. Required trackBy — @for enforces trackBy, preventing performance bugs
  2. @empty block — Built-in empty state handling
  3. Better type checking — Stricter template type inference
  4. No imports needed — Built into the compiler, no NgIf/NgFor imports
  5. Cleaner syntax — No asterisk prefix, more readable

Quiz

1. What is the new control flow syntax for conditional rendering in Angular 17+?

Question 1 options

2. What is required in @for that was optional in *ngFor?

Question 2 options

3. What is the @empty block used for?

Question 3 options

Flashcards

Question

What are the new control flow blocks?

Answer

@if, @for, @switch — built into Angular 17+ compiler, no imports needed.

Question

What is required in @for?

Answer

track expression — e.g., @for (item of items; track item.id). Enforces performance best practices.

Question

What does @empty do?

Answer

Displays content when the @for iterable has no items. Built-in empty state handling.

Revision Notes

Key Takeaways

  • 1. @if, @for, @switch replace *ngIf, *ngFor, *ngSwitch
  • 2. @for requires a track expression for performance
  • 3. @empty provides built-in empty state handling
  • 4. New control flow has no import requirements and better type checking

Interview Tips

  • • Explain why the new control flow is better than the old syntax
  • • Be ready to describe the performance benefits of required trackBy
  • • Mention that old syntax still works but new syntax is preferred

Cheat Sheet

Structural Directives Cheat Sheet

New control flow (Angular 17+):

@if (condition) { } @else { }

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

@switch (status) {
  @case ('loading') { }
  @default { }
}

Old syntax (still works):

<div *ngIf="condition">
<li *ngFor="let item of items; trackBy: fn">
<div [ngSwitch]="status">

Continue Learning