Skip to content

@if and @for

Real templates need to show and hide content based on conditions, and render lists of items. Angular provides @if and @for — built-in control flow syntax that handles both cases cleanly.

@if renders a block of template only when the condition is true:

@if (loading) {
<div class="spinner">Loading...</div>
} @else if (error) {
<p class="error">{{ error }}</p>
} @else {
<div class="grid">
<!-- movie cards here -->
</div>
}

This is much cleaner than the older *ngIf directive syntax:

<!-- old way — still works but not preferred -->
<div *ngIf="loading; else content">Loading...</div>
<ng-template #content>...</ng-template>

The @if syntax reads like familiar TypeScript/JavaScript code, without special attribute syntax or ng-template placeholders.

@if (user) {
<p>Welcome, {{ user.name }}!</p>
} @else {
<a routerLink="/login">Sign in</a>
}
@if (status === 'loading') {
<p>Loading...</p>
} @else if (status === 'error') {
<p>Something went wrong.</p>
} @else {
<p>Data loaded!</p>
}

@for iterates over an array and renders a block for each item. The track expression is required — it tells Angular how to identify each item so it can update the DOM efficiently when the list changes:

@for (movie of movies; track movie.id) {
<app-movie-card [movie]="movie" />
}

Using track movie.id means Angular can add, remove, or reorder individual cards without re-rendering the entire list.

If items do not have a unique identifier, you can use track $index (the loop index), but this is less efficient for list updates.

@for supports an @empty block that renders when the array has no items:

@for (movie of watchlist; track movie.id) {
<app-movie-card [movie]="movie" />
} @empty {
<p class="empty-state">Your watchlist is empty. Browse movies to add some.</p>
}

In CinemaVault, the Watchlist page uses this pattern to show a helpful message when no movies have been saved.

@if (loading) {
<div class="spinner"></div>
} @else {
@for (movie of movies; track movie.id) {
<app-movie-card [movie]="movie" />
} @empty {
<p>No movies found.</p>
}
}

This pattern appears on every page in CinemaVault: show a loading state while the API request is in flight, then show the results (or an empty state) when it completes.

You may encounter the older directive syntax in existing code:

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

These still work in Angular 17+. The new @if/@for syntax is a language-level feature rather than a directive, which makes it easier to use and slightly more performant. Prefer the new syntax in all new code.

  1. Add a movies: string[] = ['Inception', 'Interstellar', 'Dunkirk'] property to a practice component.
  2. Add loading = false and a button that toggles it.
  3. Use @if (loading) to show a “Loading…” message, and @else to render the list.
  4. Inside the @else, use @for (movie of movies; track movie) to display each title in a <li>.
  5. Add an @empty block that shows “No movies yet.”
  6. Test by clearing the array in the constructor and confirming the empty state appears.
  • @if (condition) { } @else { } conditionally renders template blocks — reads like JavaScript.
  • @for (item of list; track item.id) { } renders a block for each item in an array.
  • track is required — it tells Angular how to identify items for efficient DOM updates.
  • @empty { } inside @for renders when the array is empty.
  • Prefer @if/@for over the older *ngIf/*ngFor directives in new code.