Angular Control Flow
Read one Angular interview question at a time, then flip for the complete explanation, example, and code.
Read one Angular interview question at a time, then flip for the complete explanation, example, and code.
Angular Interview Questions · Angular Control Flow Companion Article
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.
Most Angular applications do not display the same HTML all the time. The UI changes according to application data.
For example:
Angular's control-flow blocks provide a clean way to implement these requirements directly inside templates.
@if conditionally renders a block of HTML.
@if (condition) {
<p>Content displayed when the condition is true.</p>
}
Example:
export class ProfileComponent {
isLoggedIn = true;
}
@if (isLoggedIn) {
<h2>Welcome back!</h2>
}
When isLoggedIn is true, Angular renders the heading. When it is false, the block is not rendered.
The condition does not have to be a simple Boolean variable. Angular can evaluate template expressions.
export class CartComponent {
cartItems = 4;
}
@if (cartItems > 0) {
<p>Your cart contains {{ cartItems }} items.</p>
}
Other common conditions include:
@if (age >= 18) {
<p>You are eligible.</p>
}
@if (username === 'admin') {
<p>Administrator account</p>
}
@if (products.length > 0) {
<p>Products are available.</p>
}
Conditions are frequently used when data is loaded asynchronously.
export class UserComponent {
user = {
name: 'Rahul',
email: 'rahul@example.com'
};
}
@if (user) {
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
}
This pattern prevents dependent UI from being displayed until the required object is available.
Angular can store the result of an expression in a local template variable using as.
@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 displays an alternative block when the preceding @if condition evaluates to false.
@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:
Welcome to your dashboard.
If it is false:
Please log in to continue.
export class ProductComponent {
inStock = false;
}
@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.
Use @else if when multiple related conditions need to be checked.
@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:
score >= 80 → falsescore >= 60 → trueGrade: BThe order therefore matters.
It works well when:
Examples include:
When the logic is based mainly on comparing one value against several fixed values, @switch may be clearer.
@for repeats a template block for every item in a collection.
It is commonly used with:
Example data:
export class StudentComponent {
students = ['Amit', 'Neha', 'Rahul', 'Priya'];
}
Template:
@for (student of students; track student) {
<p>{{ student }}</p>
}
Output:
Amit
Neha
Rahul
Priya
Angular creates one <p> element for each array item.
Real applications commonly work with arrays of objects.
export class EmployeeComponent {
employees = [
{ id: 101, name: 'Amit', department: 'Development' },
{ id: 102, name: 'Priya', department: 'Testing' },
{ id: 103, name: 'Rahul', department: 'Support' }
];
}
@for (employee of employees; track employee.id) {
<div>
<h3>{{ employee.name }}</h3>
<p>{{ employee.department }}</p>
</div>
}
Each employee receives its own rendered block.
The track expression tells Angular how an item in an @for loop should be identified.
@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:
Suppose the application has:
employees = [
{ id: 1, name: 'Amit' },
{ id: 2, name: 'Neha' },
{ id: 3, name: 'Rahul' }
];
Using:
@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.
Prefer a stable unique property such as:
track user.id
track product.productId
track order.orderNumber
A database ID is often an excellent choice.
For a collection of unique primitive values, the item itself may be used:
@for (language of languages; track language) {
<span>{{ language }}</span>
}
For collections where items never change position, $index can sometimes be used.
@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.
Angular provides special variables inside an @for block.
Important variables include:
| Variable | Meaning |
|---|---|
$index | Current zero-based position |
$first | Whether the current item is first |
$last | Whether the current item is last |
$even | Whether the index is even |
$odd | Whether the index is odd |
$count | Total number of items |
These variables exist only in the relevant loop context.
$index represents the current item's position, starting from 0.
@for (student of students; track student.id) {
<p>{{ $index }} - {{ student.name }}</p>
}
For three students, the values are:
0 - Amit
1 - Neha
2 - Rahul
For user-facing numbering, add 1.
@for (student of students; track student.id) {
<p>{{ $index + 1 }}. {{ student.name }}</p>
}
Output:
1. Amit
2. Neha
3. Rahul
$index can be used for:
Example:
<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 is true only for the first item.
@for (product of products; track product.id) {
@if ($first) {
<strong>First Product:</strong>
}
<span>{{ product.name }}</span>
}
If there are four products:
Item 1 → $first = true
Item 2 → $first = false
Item 3 → $first = false
Item 4 → $first = false
It is useful for:
$last is true only for the final item.
@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:
Cart → Address → Payment → Confirmation
Typical uses include:
$even becomes true when the current item's zero-based index is even.
For example:
Index 0 → true
Index 1 → false
Index 2 → true
Index 3 → false
Example:
@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 is the opposite of $even.
Index 0 → false
Index 1 → true
Index 2 → false
Index 3 → true
Example:
@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.
Instead of rendering completely different markup, contextual variables can participate in class bindings.
@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 contains the total number of items being iterated by the current @for block.
@for (student of students; track student.id) {
<p>
Student {{ $index + 1 }} of {{ $count }}:
{{ student.name }}
</p>
}
If the collection has four students:
Student 1 of 4
Student 2 of 4
Student 3 of 4
Student 4 of 4
Typical uses include:
Contextual variables can be assigned to local names when useful.
@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 is used with @for to display content when the collection contains no items.
@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.
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:
No products found.
Your cart is empty.
No notifications available.
No matching search results.
No employees have been added yet.
@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 selects one block from several possible values.
Basic syntax:
@switch (expression) {
@case (value1) {
...
}
@case (value2) {
...
}
@default {
...
}
}
Example:
export class OrderComponent {
orderStatus = 'shipped';
}
@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:
Your order has been shipped.
Each @case represents one possible value inside an @switch.
@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:
@if (role === 'admin') {
...
} @else if (role === 'manager') {
...
} @else if (role === 'employee') {
...
}
Use whichever structure communicates the intent more clearly.
@default provides fallback content when none of the defined @case values match.
@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.
Both can implement conditional rendering, but they are suited to different situations.
| Requirement | Better 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 status | Often @switch |
| Simple true/false UI | @if |
Example of a range:
@if (temperature > 35) {
<p>Very hot</p>
} @else if (temperature > 25) {
<p>Warm</p>
} @else {
<p>Cool</p>
}
Example of fixed values:
@switch (status) {
@case ('active') {
<p>Active</p>
}
@case ('inactive') {
<p>Inactive</p>
}
@default {
<p>Unknown</p>
}
}
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.
Suppose only active users should receive a special label.
users = [
{ id: 1, name: 'Amit', active: true },
{ id: 2, name: 'Neha', active: false },
{ id: 3, name: 'Rahul', active: true }
];
@for (user of users; track user.id) {
<div>
<span>{{ user.name }}</span>
@if (user.active) {
<strong>Active</strong>
} @else {
<span>Inactive</span>
}
</div>
}
Here:
@for creates one block for each user.@if evaluates the status of that particular user.Sometimes a collection should be displayed only when another application condition is true.
@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.
A list can contain items with different states.
@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 loops are useful with hierarchical data.
Example data:
departments = [
{
id: 1,
name: 'Development',
employees: [
{ id: 101, name: 'Amit' },
{ id: 102, name: 'Neha' }
]
},
{
id: 2,
name: 'Testing',
employees: [
{ id: 201, name: 'Rahul' }
]
}
];
Template:
@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.
Each @for block has its own contextual variables.
Consider:
@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:
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.
The following example combines several control-flow features.
export class ProductListComponent {
isLoading = false;
products = [
{
id: 101,
name: 'Laptop',
price: 65000,
stock: 4
},
{
id: 102,
name: 'Keyboard',
price: 2500,
stock: 0
}
];
}
@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:
These are typical requirements in production Angular applications.
@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.
API-driven screens usually have more than two states.
@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:
Loading
↓
Success / Error
↓
Data / Empty
Thinking explicitly about these states helps prevent incomplete interfaces.
Older Angular applications commonly use structural directives such as:
<div *ngIf="isLoggedIn">
Welcome
</div>
and:
<li *ngFor="let user of users">
{{ user.name }}
</li>
Modern Angular supports built-in block control flow:
@if (isLoggedIn) {
<div>Welcome</div>
}
@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.
The modern syntax provides several practical benefits.
@if (...) {
} @else {
}
closely resembles normal programming control flow.
@empty keeps an empty-state message directly beside its corresponding loop.
Variables such as $index, $count, $first, and $last are immediately available.
@for makes item tracking an important part of writing collection rendering logic.
Nested blocks can often be understood more quickly than deeply nested directive-based markup.
For dynamic objects, use a stable unique identifier when possible.
Better:
@for (user of users; track user.id) {
{{ user.name }}
}
$index begins at zero.
0, 1, 2, 3...
Use:
{{ $index + 1 }}
when displaying ordinary numbering to users.
This:
@if ($even) {
}
checks whether the loop index is even.
It does not test:
item % 2 === 0
If the actual numeric item should be checked, test that value explicitly.
A list may legitimately contain no items.
Instead of presenting an unexplained blank area:
@for (product of products; track product.id) {
<p>{{ product.name }}</p>
} @empty {
<p>No products available.</p>
}
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.
This is valid:
@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:
@switch (status) {
@case ('pending') {
}
@case ('approved') {
}
@case ('rejected') {
}
@default {
}
}
Choose the construct that makes the template's intention easiest to understand.
$index + 1 when displaying human-friendly numbering.$first and $last instead of manually calculating first and last positions.$count when both current position and total number are required.@switch when one value is compared against several fixed alternatives.| Requirement | Feature |
|---|---|
| Display content conditionally | @if |
| Display fallback content | @else |
| Test another condition | @else if |
| Repeat content | @for |
| Identify list items efficiently | track |
| 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 structures | Nested Control Flow |
Angular control flow can be remembered through four basic questions.
Use:
@if
@else if
@else
Use:
@for
Use:
@empty
Use:
@switch
@case
@default
The contextual variables answer additional questions about a loop:
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
export class CourseComponent {
isLoading = false;
courses = [
{
id: 1,
title: 'Angular',
level: 'advanced',
lessons: ['Components', 'Templates', 'Control Flow']
},
{
id: 2,
title: 'TypeScript',
level: 'intermediate',
lessons: []
}
];
}
@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.