AngularJSStructural Directives (*ngIf, *ngFor)

Structural Directives in Angular

Structural directives reshape the DOM by adding, removing, or manipulating elements. Unlike attribute directives that change the appearance of an existing element, structural directives decide whether an element exists in the DOM at all — or how many times it appears.

Angular's built-in structural directives are *ngIf, *ngFor, and *ngSwitch. Angular 17 also introduced new built-in control flow blocks (@if, @for, @switch) that replace them in modern code.

The Asterisk * Desugaring

The * prefix is syntactic sugar. Angular expands it into a <ng-template> with a binding on the directive. Understanding this helps you use structural directives in more advanced ways.

HTML
<!-- Short form (syntactic sugar) -->
<p *ngIf="isVisible">Hello</p>

<!-- Expanded form (what Angular really uses) -->
<ng-template [ngIf]="isVisible">
  <p>Hello</p>
</ng-template>
Note
You can never put two structural directives on the same element (*ngIf and *ngFor together). Use <ng-container> as an invisible wrapper instead.
*ngIf

*ngIf conditionally includes or removes an element from the DOM based on a truthy expression. The element is destroyed (not just hidden) when the condition is false.

TS
import { Component } from '@angular/core';
import { NgIf } from '@angular/common';

@Component({
  selector: 'app-ngif-demo',
  standalone: true,
  imports: [NgIf],
  template: `
    <!-- Basic -->
    <p *ngIf="isLoggedIn">Welcome back, {{ username }}!</p>

    <!-- With else -->
    <div *ngIf="isLoading; else content">
      <p>Loading...</p>
    </div>
    <ng-template #content>
      <p>Data loaded!</p>
    </ng-template>

    <!-- With then/else -->
    <ng-container *ngIf="status === 'active'; then activeBlock; else inactiveBlock" />
    <ng-template #activeBlock><p class="green">Active</p></ng-template>
    <ng-template #inactiveBlock><p class="red">Inactive</p></ng-template>
  `,
})
export class NgIfDemoComponent {
  isLoggedIn = true;
  username = 'Alice';
  isLoading = false;
  status = 'active';
}
Tip
Use \`*ngIf\` for conditional rendering. Use \`[hidden]="condition"\` only when you want to keep the element in the DOM (e.g. to preserve form state or avoid re-initialisation costs).
*ngFor

*ngFor repeats a template for each item in an iterable. It exposes several local variables you can use inside the loop.

TS
import { Component } from '@angular/core';
import { NgFor } from '@angular/common';

interface Product {
  id: number;
  name: string;
  price: number;
  inStock: boolean;
}

@Component({
  selector: 'app-product-list',
  standalone: true,
  imports: [NgFor],
  template: `
    <ul>
      <li *ngFor="
        let product of products;
        let i = index;
        let first = first;
        let last = last;
        let even = even;
        let odd = odd;
        trackBy: trackById
      " [class.even-row]="even" [class.first-item]="first">

        {{ i + 1 }}. {{ product.name }} — {{ product.price | currency }}
        <span *ngIf="!product.inStock" class="out-of-stock">Out of stock</span>
        <em *ngIf="last">(last item)</em>
      </li>
    </ul>
  `,
})
export class ProductListComponent {
  products: Product[] = [
    { id: 1, name: 'Laptop',  price: 999,  inStock: true  },
    { id: 2, name: 'Monitor', price: 349,  inStock: true  },
    { id: 3, name: 'Webcam',  price: 89,   inStock: false },
    { id: 4, name: 'Headset', price: 149,  inStock: true  },
  ];

  trackById(index: number, product: Product) {
    return product.id;
  }
}
Warning
Always provide a \`trackBy\` function for lists that change at runtime. Without it Angular destroys and recreates all DOM nodes on every change — which is slow and loses focus/animation state.
*ngFor Local Variables Reference

Variable

Type

Value

index

number

Current iteration index (0-based)

count

number

Total number of items

first

boolean

true for the first item

last

boolean

true for the last item

even

boolean

true for even-indexed items (0, 2, 4...)

odd

boolean

true for odd-indexed items (1, 3, 5...)

*ngSwitch

*ngSwitch is the structural equivalent of a switch statement — it renders one of several templates based on a matching expression.

TS
import { Component } from '@angular/core';
import { NgSwitch, NgSwitchCase, NgSwitchDefault } from '@angular/common';

@Component({
  selector: 'app-status-badge',
  standalone: true,
  imports: [NgSwitch, NgSwitchCase, NgSwitchDefault],
  template: `
    <div [ngSwitch]="orderStatus">
      <span *ngSwitchCase="'pending'"   class="badge yellow">Pending</span>
      <span *ngSwitchCase="'shipped'"   class="badge blue">Shipped</span>
      <span *ngSwitchCase="'delivered'" class="badge green">Delivered</span>
      <span *ngSwitchCase="'cancelled'" class="badge red">Cancelled</span>
      <span *ngSwitchDefault            class="badge grey">Unknown</span>
    </div>

    <select [(ngModel)]="orderStatus">
      <option>pending</option>
      <option>shipped</option>
      <option>delivered</option>
      <option>cancelled</option>
    </select>
  `,
})
export class StatusBadgeComponent {
  orderStatus = 'pending';
}
ng-container — The Invisible Wrapper

<ng-container> is a grouping element that Angular removes from the DOM at render time. It has no HTML equivalent. Use it when you need to apply a structural directive without adding an extra DOM element.

HTML
<!-- Apply *ngIf without a wrapper element -->
<ng-container *ngIf="user">
  <p>Name: {{ user.name }}</p>
  <p>Email: {{ user.email }}</p>
</ng-container>

<!-- Apply *ngFor without a wrapping <div> -->
<table>
  <ng-container *ngFor="let row of rows">
    <tr><td>{{ row.col1 }}</td><td>{{ row.col2 }}</td></tr>
  </ng-container>
</table>

<!-- Combine *ngIf and *ngFor on separate elements -->
<ng-container *ngFor="let item of items">
  <div *ngIf="item.visible">{{ item.label }}</div>
</ng-container>
Note
<ng-template> is similar but is never rendered on its own. Use <ng-container> for grouping elements; use <ng-template> for defining lazy/reusable template fragments.
Creating a Custom Structural Directive

You can write your own structural directive. The key is injecting TemplateRef (the <ng-template> content) and ViewContainerRef (where to stamp it into the DOM).

TS
// repeat.directive.ts
import {
  Directive,
  Input,
  TemplateRef,
  ViewContainerRef,
  OnChanges,
} from '@angular/core';

@Directive({
  selector: '[appRepeat]',
  standalone: true,
})
export class RepeatDirective implements OnChanges {
  @Input('appRepeat') times = 0;

  constructor(
    private templateRef: TemplateRef<{ $implicit: number }>,
    private viewContainer: ViewContainerRef,
  ) {}

  ngOnChanges() {
    this.viewContainer.clear();
    for (let i = 0; i < this.times; i++) {
      this.viewContainer.createEmbeddedView(this.templateRef, { $implicit: i });
    }
  }
}

HTML
<!-- Renders "Item 0", "Item 1", "Item 2" -->
<p *appRepeat="3; let i">Item {{ i }}</p>

<!-- Renders 5 stars -->
<span *appRepeat="5">★</span>
Tip
The \`\$implicit\` context variable is what you expose as \`let varName\` in the template. Additional named variables can be added to the context object and accessed with \`let varName = contextKey\`.
Modern Alternative: Built-in Control Flow

Angular 17 introduced @if, @for, and @switch as native template syntax, replacing structural directives in new code. They require no imports and have better type narrowing.

HTML
<!-- @if replaces *ngIf -->
@if (isLoggedIn) {
  <p>Welcome!</p>
} @else {
  <p>Please log in.</p>
}

<!-- @for replaces *ngFor (track is required) -->
@for (item of items; track item.id) {
  <li>{{ item.name }}</li>
} @empty {
  <p>No items found.</p>
}

<!-- @switch replaces *ngSwitch -->
@switch (role) {
  @case ('admin')  { <app-admin-panel /> }
  @case ('editor') { <app-editor-panel /> }
  @default         { <app-viewer-panel /> }
}
Summary
  • *ngIf conditionally adds or removes elements from the DOM.

  • *ngFor repeats a template for each item; always use trackBy to optimise re-renders.

  • *ngSwitch renders one of several templates based on a matching value.

  • The * prefix desugars to an <ng-template> — you can use the long form for advanced cases.

  • <ng-container> is an invisible wrapper; use it to avoid adding unwanted DOM elements.

  • Custom structural directives use TemplateRef + ViewContainerRef.

  • Angular 17+ @if / @for / @switch are the preferred replacement for structural directives.