Angular Structural Directives

Structural directives and Angular's newer control-flow blocks change the DOM's structure by adding, removing, or repeating elements.

Two Ways to Control Structure

Angular offers two syntaxes for conditionally rendering or repeating content: the classic asterisk-prefixed directives (*ngIf, *ngFor) and the newer built-in control flow blocks (@if, @for) introduced in Angular 17. Both achieve the same goal; the block syntax is now the recommended default for new code.

Conditional Rendering

*ngIf removes an element from the DOM entirely when its expression is falsy, rather than just hiding it with CSS - this matters for performance and for any logic tied to the element's presence, such as form validation.

*ngIf (classic syntax)

import { Component } from '@angular/core';
import { NgIf } from '@angular/common';
import { bootstrapApplication } from '@angular/platform-browser';

@Component({
  selector: 'app-status',
  standalone: true,
  imports: [NgIf],
  template: `
    <p *ngIf="isOnline; else offlineBlock">User is online</p>
    <ng-template #offlineBlock>
      <p>User is offline</p>
    </ng-template>
  `
})
export class StatusComponent {
  isOnline = true;
}

bootstrapApplication(StatusComponent).catch(err => console.error(err));

@if (modern control flow)

import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';

@Component({
  selector: 'app-status',
  standalone: true,
  template: `
    @if (isOnline) {
      <p>User is online</p>
    } @else {
      <p>User is offline</p>
    }
  `
})
export class StatusComponent {
  isOnline = true;
}

bootstrapApplication(StatusComponent).catch(err => console.error(err));
Note: @if and @for require no imports array entry and no CommonModule - they are built directly into the template compiler, which is one reason the Angular team recommends them going forward.

Repeating Elements

*ngFor and @for both render one copy of a template for each item in a collection. The modern @for block requires an explicit track expression, which helps Angular efficiently update the DOM when the list changes instead of re-rendering everything.

@for with track

import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';

interface Task {
  id: number;
  title: string;
}

@Component({
  selector: 'app-task-list',
  standalone: true,
  template: `
    <ul>
      @for (task of tasks; track task.id) {
        <li>{{ task.title }}</li>
      } @empty {
        <li>No tasks yet</li>
      }
    </ul>
  `
})
export class TaskListComponent {
  tasks: Task[] = [
    { id: 1, title: 'Write templates lesson' },
    { id: 2, title: 'Review pipes lesson' }
  ];
}

bootstrapApplication(TaskListComponent).catch(err => console.error(err));
  • *ngIf / @if - render an element only when a condition is true
  • *ngFor / @for - repeat an element once per item in a list
  • @else / *ngIf...else - render an alternative when the condition is false
  • @empty - renders when a @for collection has zero items
  • track - required in @for; helps Angular identify items across re-renders
Feature*ngIf / *ngFor@if / @for
Import requiredYes (NgIf, NgFor, or CommonModule)No, built into the compiler
Syntax styleHTML attribute prefixed with *Block syntax with @ keyword
Empty list handlingManual, with *ngIf on a separate elementBuilt-in @empty block
Recommended for new codeNo (still supported)Yes
Note: *ngIf and *ngFor still work and are widely used in existing codebases, but mixing both syntaxes on the same element is not allowed - each element can use only one structural directive.
Note: Always provide a meaningful track expression in @for, such as an item's unique id. Using track $index works but defeats some of the performance benefit when items are reordered.