Angular Control Flow

Read one Angular interview question at a time, then flip for the complete explanation, example, and code.

0/55 Known filtered set
Level
Read Full Guide Question: 1 of 55

Angular Interview Questions · Angular Control Flow Companion Article

Angular Control Flow

Angular control flow allows a template to decide what should be displayed, how many times content should be repeated, and which section should be rendered for a particular value. Modern Angular provides built-in block syntax such as @if, @for, and @switch, making template logic easier to read and maintain. Control flow belongs in the template layer. It is useful for showing loading states, permissions, empty lists, validation messages, dashboards, menus, product lists, search results, and many other dynamic UI elements.

Why Control Flow Is Important in Angular

Most Angular applications do not display the same HTML all the time. The UI changes according to application data.

For example:

  • Display a welcome message only after login.
  • Show an error message when an API request fails.
  • Display products received from the backend.
  • Show "No records found" when a list is empty.
  • Display different controls for administrators and normal users.
  • Render different content depending on order status.
  • Highlight alternate rows in a table.
  • Display special content for the first or last item.

Angular's control-flow blocks provide a clean way to implement these requirements directly inside templates.

@if

@if conditionally renders a block of HTML.

Basic Syntax

HTML
@if (condition) {
  <p>Content displayed when the condition is true.</p>
}

Example:

TypeScript
export class ProfileComponent {
  isLoggedIn = true;
}
HTML
@if (isLoggedIn) {
  <h2>Welcome back!</h2>
}

When isLoggedIn is true, Angular renders the heading. When it is false, the block is not rendered.

Using Expressions with @if

The condition does not have to be a simple Boolean variable. Angular can evaluate template expressions.

TypeScript
export class CartComponent {
  cartItems = 4;
}
HTML
@if (cartItems > 0) {
  <p>Your cart contains {{ cartItems }} items.</p>
}

Other common conditions include:

HTML
@if (age >= 18) {
  <p>You are eligible.</p>
}
HTML
@if (username === 'admin') {
  <p>Administrator account</p>
}
HTML
@if (products.length > 0) {
  <p>Products are available.</p>
}

Using @if with Object Data

Conditions are frequently used when data is loaded asynchronously.

TypeScript
export class UserComponent {
  user = {
    name: 'Rahul',
    email: 'rahul@example.com'
  };
}
HTML
@if (user) {
  <h3>{{ user.name }}</h3>
  <p>{{ user.email }}</p>
}

This pattern prevents dependent UI from being displayed until the required object is available.

Aliasing an @if Expression

Angular can store the result of an expression in a local template variable using as.

HTML
@if (user.profile; as profile) {
  <h3>{{ profile.name }}</h3>
  <p>{{ profile.email }}</p>
}

This can make templates cleaner when the same expression would otherwise be repeated several times.

@else

@else displays an alternative block when the preceding @if condition evaluates to false.

HTML
@if (isLoggedIn) {
  <p>Welcome to your dashboard.</p>
} @else {
  <p>Please log in to continue.</p>
}

Only one block is rendered.

If isLoggedIn is true:

Text
Welcome to your dashboard.

If it is false:

Text
Please log in to continue.

Practical @if and @else Example

TypeScript
export class ProductComponent {
  inStock = false;
}
HTML
@if (inStock) {
  <button>Add to Cart</button>
} @else {
  <p>Currently out of stock.</p>
}

This type of conditional rendering is common in e-commerce applications.

@else if

Use @else if when multiple related conditions need to be checked.

HTML
@if (score >= 80) {
  <p>Grade: A</p>
} @else if (score >= 60) {
  <p>Grade: B</p>
} @else if (score >= 40) {
  <p>Grade: C</p>
} @else {
  <p>Grade: Fail</p>
}

Angular checks conditions from top to bottom.

For score = 72:

  1. score >= 80 → false
  2. score >= 60 → true
  3. Angular renders Grade: B
  4. Remaining branches are ignored

The order therefore matters.

When to Use @else if

It works well when:

  • conditions are ranges;
  • one condition should exclude the others;
  • conditions are related to each other;
  • only one result should be displayed.

Examples include:

  • marks and grades;
  • age categories;
  • authentication states;
  • account levels;
  • stock quantities;
  • performance ratings.

When the logic is based mainly on comparing one value against several fixed values, @switch may be clearer.

@for

@for repeats a template block for every item in a collection.

It is commonly used with:

  • arrays;
  • API response lists;
  • navigation items;
  • tables;
  • products;
  • users;
  • notifications;
  • cards.

Example data:

TypeScript
export class StudentComponent {
  students = ['Amit', 'Neha', 'Rahul', 'Priya'];
}

Template:

HTML
@for (student of students; track student) {
  <p>{{ student }}</p>
}

Output:

Text
Amit
Neha
Rahul
Priya

Angular creates one <p> element for each array item.

Looping Through Objects

Real applications commonly work with arrays of objects.

TypeScript
export class EmployeeComponent {
  employees = [
    { id: 101, name: 'Amit', department: 'Development' },
    { id: 102, name: 'Priya', department: 'Testing' },
    { id: 103, name: 'Rahul', department: 'Support' }
  ];
}
HTML
@for (employee of employees; track employee.id) {
  <div>
    <h3>{{ employee.name }}</h3>
    <p>{{ employee.department }}</p>
  </div>
}

Each employee receives its own rendered block.

track

The track expression tells Angular how an item in an @for loop should be identified.

HTML
@for (employee of employees; track employee.id) {
  <p>{{ employee.name }}</p>
}

Here Angular identifies each employee using employee.id.

This becomes particularly important when items are:

  • inserted;
  • deleted;
  • reordered;
  • updated;
  • refreshed from an API.

Why track Matters

Suppose the application has:

TypeScript
employees = [
  { id: 1, name: 'Amit' },
  { id: 2, name: 'Neha' },
  { id: 3, name: 'Rahul' }
];

Using:

HTML
@for (employee of employees; track employee.id) {
  <p>{{ employee.name }}</p>
}

allows Angular to associate rendered DOM elements with stable employee IDs.

If only employee 2 changes, Angular can more accurately determine which rendered item corresponds to the changed record rather than treating every record as completely unrelated.

Choosing a Good Tracking Value

Prefer a stable unique property such as:

HTML
track user.id
HTML
track product.productId
HTML
track order.orderNumber

A database ID is often an excellent choice.

For a collection of unique primitive values, the item itself may be used:

HTML
@for (language of languages; track language) {
  <span>{{ language }}</span>
}

Tracking with $index

For collections where items never change position, $index can sometimes be used.

HTML
@for (month of months; track $index) {
  <p>{{ month }}</p>
}

However, a stable unique item identifier is usually a better choice for dynamic data where records may be inserted, removed, or reordered.

Contextual Variables in @for

Angular provides special variables inside an @for block.

Important variables include:

VariableMeaning
$indexCurrent zero-based position
$firstWhether the current item is first
$lastWhether the current item is last
$evenWhether the index is even
$oddWhether the index is odd
$countTotal number of items

These variables exist only in the relevant loop context.

$index

$index represents the current item's position, starting from 0.

HTML
@for (student of students; track student.id) {
  <p>{{ $index }} - {{ student.name }}</p>
}

For three students, the values are:

Text
0 - Amit
1 - Neha
2 - Rahul

For user-facing numbering, add 1.

HTML
@for (student of students; track student.id) {
  <p>{{ $index + 1 }}. {{ student.name }}</p>
}

Output:

Text
1. Amit
2. Neha
3. Rahul

Practical Uses of $index

$index can be used for:

  • serial numbers;
  • table row numbers;
  • numbered cards;
  • step indicators;
  • labels;
  • simple display positions.

Example:

HTML
<tr>
  <th>No.</th>
  <th>Name</th>
</tr>

@for (employee of employees; track employee.id) {
  <tr>
    <td>{{ $index + 1 }}</td>
    <td>{{ employee.name }}</td>
  </tr>
}

$first

$first is true only for the first item.

HTML
@for (product of products; track product.id) {
  @if ($first) {
    <strong>First Product:</strong>
  }

  <span>{{ product.name }}</span>
}

If there are four products:

Text
Item 1 → $first = true
Item 2 → $first = false
Item 3 → $first = false
Item 4 → $first = false

It is useful for:

  • highlighting the first result;
  • adding a special label;
  • applying first-item layout logic;
  • identifying the beginning of a sequence.

$last

$last is true only for the final item.

HTML
@for (step of steps; track step.id) {
  <span>{{ step.name }}</span>

  @if (!$last) {
    <span> → </span>
  }
}

The arrow appears between steps but not after the final step.

Example output:

Text
Cart → Address → Payment → Confirmation

Typical uses include:

  • avoiding a separator after the final item;
  • highlighting the final result;
  • handling breadcrumbs;
  • rendering timeline endings.

$even

$even becomes true when the current item's zero-based index is even.

For example:

Text
Index 0 → true
Index 1 → false
Index 2 → true
Index 3 → false

Example:

HTML
@for (employee of employees; track employee.id) {
  @if ($even) {
    <p>Even index: {{ employee.name }}</p>
  }
}

Remember that $even refers to the index, not whether a numeric item value itself is even.

$odd

$odd is the opposite of $even.

Text
Index 0 → false
Index 1 → true
Index 2 → false
Index 3 → true

Example:

HTML
@for (employee of employees; track employee.id) {
  @if ($odd) {
    <p>Odd index: {{ employee.name }}</p>
  }
}

$even and $odd are commonly useful when alternating presentation or behavior between rows.

Using $even and $odd for Alternating Classes

Instead of rendering completely different markup, contextual variables can participate in class bindings.

HTML
@for (employee of employees; track employee.id) {
  <div [class.even-row]="$even" [class.odd-row]="$odd">
    {{ employee.name }}
  </div>
}

This keeps the actual employee markup in one place.

$count

$count contains the total number of items being iterated by the current @for block.

HTML
@for (student of students; track student.id) {
  <p>
    Student {{ $index + 1 }} of {{ $count }}:
    {{ student.name }}
  </p>
}

If the collection has four students:

Text
Student 1 of 4
Student 2 of 4
Student 3 of 4
Student 4 of 4

Typical uses include:

  • progress indicators;
  • result counters;
  • step numbers;
  • pagination-style labels;
  • displaying current position relative to total records.

Aliasing Contextual Variables

Contextual variables can be assigned to local names when useful.

HTML
@for (product of products; track product.id; let i = $index, total = $count) {
  <p>{{ i + 1 }} of {{ total }} - {{ product.name }}</p>
}

Aliases become particularly helpful in nested loops where multiple loop levels have their own contextual variables.

@empty

@empty is used with @for to display content when the collection contains no items.

HTML
@for (product of products; track product.id) {
  <p>{{ product.name }}</p>
} @empty {
  <p>No products available.</p>
}

This keeps the list and its empty state together.

Why @empty Is Useful

Without an empty-state branch, a blank collection may produce an empty area with no explanation.

A good empty state tells users what happened.

Examples:

Text
No products found.
Text
Your cart is empty.
Text
No notifications available.
Text
No matching search results.
Text
No employees have been added yet.

Search Results Example

HTML
@for (result of searchResults; track result.id) {
  <article>
    <h3>{{ result.title }}</h3>
    <p>{{ result.description }}</p>
  </article>
} @empty {
  <p>No results matched your search.</p>
}

This provides immediate feedback instead of leaving the user with a blank page.

@switch

@switch selects one block from several possible values.

Basic syntax:

HTML
@switch (expression) {
  @case (value1) {
    ...
  }

  @case (value2) {
    ...
  }

  @default {
    ...
  }
}

Example:

TypeScript
export class OrderComponent {
  orderStatus = 'shipped';
}
HTML
@switch (orderStatus) {
  @case ('pending') {
    <p>Your order is being processed.</p>
  }

  @case ('shipped') {
    <p>Your order has been shipped.</p>
  }

  @case ('delivered') {
    <p>Your order has been delivered.</p>
  }

  @default {
    <p>Unknown order status.</p>
  }
}

Because orderStatus is shipped, Angular displays:

Text
Your order has been shipped.

@case

Each @case represents one possible value inside an @switch.

HTML
@switch (role) {
  @case ('admin') {
    <p>Administrator Dashboard</p>
  }

  @case ('manager') {
    <p>Manager Dashboard</p>
  }

  @case ('employee') {
    <p>Employee Dashboard</p>
  }
}

This is often clearer than writing many equality checks:

HTML
@if (role === 'admin') {
  ...
} @else if (role === 'manager') {
  ...
} @else if (role === 'employee') {
  ...
}

Use whichever structure communicates the intent more clearly.

@default

@default provides fallback content when none of the defined @case values match.

HTML
@switch (paymentStatus) {
  @case ('success') {
    <p>Payment completed.</p>
  }

  @case ('failed') {
    <p>Payment failed.</p>
  }

  @case ('pending') {
    <p>Payment is pending.</p>
  }

  @default {
    <p>Payment status is unavailable.</p>
  }
}

Providing a default state is especially valuable when values can come from an external API.

Unexpected data will still result in meaningful UI instead of an unexplained blank area.

@if vs @switch

Both can implement conditional rendering, but they are suited to different situations.

RequirementBetter Choice
Check one Boolean condition@if
Compare numeric ranges@if / @else if
Check multiple unrelated expressions@if
Compare one variable against fixed values@switch
Render by role or statusOften @switch
Simple true/false UI@if

Example of a range:

HTML
@if (temperature > 35) {
  <p>Very hot</p>
} @else if (temperature > 25) {
  <p>Warm</p>
} @else {
  <p>Cool</p>
}

Example of fixed values:

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

  @case ('inactive') {
    <p>Inactive</p>
  }

  @default {
    <p>Unknown</p>
  }
}

Nested Control Flow

Angular control-flow blocks can be placed inside other control-flow blocks.

For example:

  • @if inside @for;
  • @for inside @if;
  • @switch inside @for;
  • @for inside another @for.

This is known as nested control flow.

@if Inside @for

Suppose only active users should receive a special label.

TypeScript
users = [
  { id: 1, name: 'Amit', active: true },
  { id: 2, name: 'Neha', active: false },
  { id: 3, name: 'Rahul', active: true }
];
HTML
@for (user of users; track user.id) {
  <div>
    <span>{{ user.name }}</span>

    @if (user.active) {
      <strong>Active</strong>
    } @else {
      <span>Inactive</span>
    }
  </div>
}

Here:

  1. @for creates one block for each user.
  2. @if evaluates the status of that particular user.

@for Inside @if

Sometimes a collection should be displayed only when another application condition is true.

HTML
@if (isLoggedIn) {
  <h2>Your Notifications</h2>

  @for (notification of notifications; track notification.id) {
    <p>{{ notification.message }}</p>
  } @empty {
    <p>No notifications.</p>
  }
} @else {
  <p>Please log in to view notifications.</p>
}

This combines authentication logic, iteration, and an empty state.

@switch Inside @for

A list can contain items with different states.

HTML
@for (order of orders; track order.id) {
  <h3>Order #{{ order.id }}</h3>

  @switch (order.status) {
    @case ('pending') {
      <p>Processing order...</p>
    }

    @case ('shipped') {
      <p>Order is on the way.</p>
    }

    @case ('delivered') {
      <p>Order delivered successfully.</p>
    }

    @default {
      <p>Status unavailable.</p>
    }
  }
}

The switch expression is evaluated separately for every order.

Nested @for Blocks

Nested loops are useful with hierarchical data.

Example data:

TypeScript
departments = [
  {
    id: 1,
    name: 'Development',
    employees: [
      { id: 101, name: 'Amit' },
      { id: 102, name: 'Neha' }
    ]
  },
  {
    id: 2,
    name: 'Testing',
    employees: [
      { id: 201, name: 'Rahul' }
    ]
  }
];

Template:

HTML
@for (department of departments; track department.id) {
  <h2>{{ department.name }}</h2>

  @for (employee of department.employees; track employee.id) {
    <p>{{ employee.name }}</p>
  } @empty {
    <p>No employees in this department.</p>
  }
}

The outer loop renders departments, while the inner loop renders employees belonging to each department.

Context Variables in Nested Loops

Each @for block has its own contextual variables.

Consider:

HTML
@for (department of departments; track department.id; let departmentIndex = $index) {
  <h2>Department {{ departmentIndex + 1 }}: {{ department.name }}</h2>

  @for (employee of department.employees; track employee.id; let employeeIndex = $index) {
    <p>
      {{ departmentIndex + 1 }}.{{ employeeIndex + 1 }}
      {{ employee.name }}
    </p>
  }
}

Possible output:

Text
Department 1: Development
1.1 Amit
1.2 Neha

Department 2: Testing
2.1 Rahul

Giving contextual variables descriptive aliases avoids confusion in nested templates.

Real-World Example: Product Listing

The following example combines several control-flow features.

TypeScript
export class ProductListComponent {
  isLoading = false;

  products = [
    {
      id: 101,
      name: 'Laptop',
      price: 65000,
      stock: 4
    },
    {
      id: 102,
      name: 'Keyboard',
      price: 2500,
      stock: 0
    }
  ];
}
HTML
@if (isLoading) {
  <p>Loading products...</p>
} @else {
  @for (product of products; track product.id) {
    <article>
      <h3>{{ product.name }}</h3>
      <p>₹{{ product.price }}</p>

      @if (product.stock > 0) {
        <p>In Stock: {{ product.stock }}</p>
        <button>Add to Cart</button>
      } @else {
        <p>Out of Stock</p>
      }
    </article>
  } @empty {
    <p>No products are currently available.</p>
  }
}

This template handles:

  • loading state;
  • product iteration;
  • stable tracking;
  • stock availability;
  • empty collections.

These are typical requirements in production Angular applications.

Real-World Example: User Dashboard

HTML
@if (user) {
  <h2>Welcome, {{ user.name }}</h2>

  @switch (user.role) {
    @case ('admin') {
      <p>You have administrator access.</p>
    }

    @case ('manager') {
      <p>You have manager access.</p>
    }

    @case ('employee') {
      <p>You have employee access.</p>
    }

    @default {
      <p>Your account role could not be identified.</p>
    }
  }

  <h3>Recent Activities</h3>

  @for (activity of user.activities; track activity.id) {
    <p>
      {{ $index + 1 }} of {{ $count }}:
      {{ activity.description }}
    </p>
  } @empty {
    <p>No recent activity.</p>
  }
} @else {
  <p>User information is unavailable.</p>
}

Several blocks cooperate without requiring the component to manually build separate HTML fragments.

Control Flow with Loading, Error, and Success States

API-driven screens usually have more than two states.

HTML
@if (isLoading) {
  <p>Loading data...</p>
} @else if (errorMessage) {
  <p>{{ errorMessage }}</p>
} @else if (users.length > 0) {
  @for (user of users; track user.id) {
    <p>{{ user.name }}</p>
  }
} @else {
  <p>No users found.</p>
}

A common UI state model is:

Text
Loading
   ↓
Success / Error
   ↓
Data / Empty

Thinking explicitly about these states helps prevent incomplete interfaces.

Modern Control Flow and Older Angular Templates

Older Angular applications commonly use structural directives such as:

HTML
<div *ngIf="isLoggedIn">
  Welcome
</div>

and:

HTML
<li *ngFor="let user of users">
  {{ user.name }}
</li>

Modern Angular supports built-in block control flow:

HTML
@if (isLoggedIn) {
  <div>Welcome</div>
}
HTML
@for (user of users; track user.id) {
  <li>{{ user.name }}</li>
}

When reading existing Angular projects, developers should therefore understand both styles, even when new code uses the modern block syntax.

Advantages of Modern Angular Control Flow

The modern syntax provides several practical benefits.

Clear visual structure

HTML
@if (...) {
} @else {
}

closely resembles normal programming control flow.

Convenient empty-list handling

@empty keeps an empty-state message directly beside its corresponding loop.

Built-in loop context

Variables such as $index, $count, $first, and $last are immediately available.

Explicit tracking

@for makes item tracking an important part of writing collection rendering logic.

Easier nested templates

Nested blocks can often be understood more quickly than deeply nested directive-based markup.

Common Mistakes

Forgetting an appropriate track expression

For dynamic objects, use a stable unique identifier when possible.

Better:

HTML
@for (user of users; track user.id) {
  {{ user.name }}
}

Confusing $index with user-facing numbering

$index begins at zero.

Text
0, 1, 2, 3...

Use:

HTML
{{ $index + 1 }}

when displaying ordinary numbering to users.

Thinking $even checks the item value

This:

HTML
@if ($even) {
}

checks whether the loop index is even.

It does not test:

TypeScript
item % 2 === 0

If the actual numeric item should be checked, test that value explicitly.

Forgetting the empty state

A list may legitimately contain no items.

Instead of presenting an unexplained blank area:

HTML
@for (product of products; track product.id) {
  <p>{{ product.name }}</p>
} @empty {
  <p>No products available.</p>
}

Making templates excessively complicated

Nested control flow is useful, but too much business logic inside HTML makes templates difficult to maintain.

Instead of repeatedly performing complex calculations in a template, calculate business-specific values in TypeScript and expose simpler state to the template.

Using the wrong conditional structure

This is valid:

HTML
@if (status === 'pending') {
} @else if (status === 'approved') {
} @else if (status === 'rejected') {
}

But when many branches compare the same variable to known fixed values, this can be easier to understand:

HTML
@switch (status) {
  @case ('pending') {
  }

  @case ('approved') {
  }

  @case ('rejected') {
  }

  @default {
  }
}

Choose the construct that makes the template's intention easiest to understand.

Good Practices

  • Keep conditions simple and readable.
  • Prefer stable IDs when tracking dynamic objects.
  • Provide meaningful empty states for collections.
  • Use $index + 1 when displaying human-friendly numbering.
  • Use $first and $last instead of manually calculating first and last positions.
  • Use $count when both current position and total number are required.
  • Use descriptive aliases in nested loops.
  • Prefer @switch when one value is compared against several fixed alternatives.
  • Avoid putting complex business rules directly inside templates.
  • Include loading, error, success, and empty states when displaying remote data.
  • Keep deeply nested control flow to a reasonable level.
  • Use reusable child components when a repeated block becomes large or complicated.

Choosing the Correct Angular Control-Flow Feature

RequirementFeature
Display content conditionally@if
Display fallback content@else
Test another condition@else if
Repeat content@for
Identify list items efficientlytrack
Get current position$index
Detect first record$first
Detect last record$last
Detect even index$even
Detect odd index$odd
Get total records$count
Handle an empty collection@empty
Compare one value with several alternatives@switch
Define an individual switch value@case
Handle unmatched switch values@default
Combine multiple control structuresNested Control Flow

Practical Mental Model

Angular control flow can be remembered through four basic questions.

Should this content exist?

Use:

Text
@if
@else if
@else

How many times should this content appear?

Use:

Text
@for

What should happen if there are no records?

Use:

Text
@empty

Which version of this content should appear?

Use:

Text
@switch
@case
@default

The contextual variables answer additional questions about a loop:

Text
Where am I?        → $index
Am I first?        → $first
Am I last?         → $last
Is my index even?  → $even
Is my index odd?   → $odd
How many items?    → $count

Final Example Combining the Chapter Concepts

TypeScript
export class CourseComponent {
  isLoading = false;

  courses = [
    {
      id: 1,
      title: 'Angular',
      level: 'advanced',
      lessons: ['Components', 'Templates', 'Control Flow']
    },
    {
      id: 2,
      title: 'TypeScript',
      level: 'intermediate',
      lessons: []
    }
  ];
}
HTML
@if (isLoading) {
  <p>Loading courses...</p>
} @else {
  @for (
    course of courses;
    track course.id;
    let courseNumber = $index
  ) {
    <section>
      <h2>{{ courseNumber + 1 }}. {{ course.title }}</h2>

      @switch (course.level) {
        @case ('beginner') {
          <p>Suitable for beginners.</p>
        }

        @case ('intermediate') {
          <p>Some previous knowledge is recommended.</p>
        }

        @case ('advanced') {
          <p>Designed for experienced learners.</p>
        }

        @default {
          <p>Course level not specified.</p>
        }
      }

      <h3>Lessons</h3>

      @for (
        lesson of course.lessons;
        track lesson;
        let lessonNumber = $index
      ) {
        <p>
          {{ lessonNumber + 1 }} of {{ $count }}:
          {{ lesson }}

          @if ($first) {
            <strong> - Start here</strong>
          }

          @if ($last) {
            <strong> - Final lesson</strong>
          }
        </p>
      } @empty {
        <p>Lessons will be added soon.</p>
      }
    </section>
  } @empty {
    <p>No courses are currently available.</p>
  }
}

This example demonstrates how Angular control-flow features can work together to create a complete dynamic interface: conditional loading, collection rendering, stable tracking, contextual loop information, empty states, value-based switching, and nested control flow.

Question Hint