Angular Lists
Learn Angular list rendering with modern @for, track expressions, contextual variables, @empty, traditional *ngFor, reusable item components, pagination, infinite scrolling, virtual scrolling, Signals, and performance best practices.
Learning Objectives
By the end of this lesson, you will understand:
- What list rendering means in Angular.
- How to render collections dynamically.
- How modern Angular
@forcontrol flow works. - How to use the
trackexpression correctly. - How to access
$index,$first,$last,$even, and$odd. - How to handle empty collections using
@empty. - How traditional
*ngForworks. - How to render lists of complex objects.
- How to combine lists with conditional rendering.
- How to build reusable list and item components.
- How to handle large datasets efficiently.
- When to use pagination, infinite scrolling, and virtual scrolling.
- Common performance mistakes.
- Frequently asked Angular list-rendering interview questions.
Introduction
Imagine opening an e-commerce application.
You search for:
Laptop
The backend returns 1,000 products.
Each product contains Product ID, Product Name, Image, Price, Rating, Stock, Discount, and Delivery Information.
Should developers manually create 1,000 product cards?
Of course not.
Instead, the backend returns a collection of products, and Angular dynamically renders the user interface for every item.
Backend API│▼Product Collection│▼Angular Component│▼@for│▼Product Card│▼Browser
This process is known as List Rendering.
Lists appear everywhere in modern applications: products, customers, transactions, orders, notifications, employees, messages, search results, and audit logs.
Understanding how to render and optimize collections is therefore essential for professional Angular development.
A Real-World Story
Imagine you're developing an online banking application.
A customer opens the Transaction History page.
The backend returns:
10,000 Transactions
Each transaction contains Transaction ID, Date, Description, Amount, Type, and Status.
A beginner might attempt to render all 10,000 records immediately.
That could lead to slow initial rendering, thousands of DOM elements, high memory consumption, poor scrolling performance, and slower updates.
A professional Angular application considers:
How many records should be displayed?Should we use pagination?Should we use virtual scrolling?How should Angular identify each record?Should data be loaded incrementally?
List rendering isn't only about displaying an array.
At scale, it becomes a performance and architecture problem.
What Is List Rendering?
List rendering is the process of dynamically creating UI elements for items in a collection.
Suppose your component contains:
products = ['Laptop','Mobile','Tablet','Headphones'];
Instead of manually writing repeated HTML, you can use Angular's modern @for control flow:
@for (product of products; track product) {<p>{{ product }}</p>}
Angular dynamically creates one paragraph for every product.
List Rendering Architecture
The basic flow looks like:
List Architecture
Collection Data to DOM Nodes
Whenever the collection changes, Angular updates the rendered list.
Modern Angular List Rendering with @for
Modern Angular provides built-in control flow using @for.
Basic syntax:
@for (item of items; track item) {<!-- Render item -->}
Example:
export class ProductComponent {products = ['Laptop','Mobile','Tablet'];}
@for (product of products; track product) {<p>{{ product }}</p>}
Output:
LaptopMobileTablet
Rendering Objects
Real applications rarely work with simple string arrays. Usually, the backend returns objects.
products = [{ id: 101, name: 'Laptop', price: 75000 },{ id: 102, name: 'Mobile', price: 45000 },{ id: 103, name: 'Tablet', price: 30000 }];
@for (product of products;track product.id) {<div class="product-card"><h3>{{ product.name }}</h3><p>₹{{ product.price }}</p></div>}
Angular creates one product card for every product.
Understanding track
One of the most important concepts in Angular list rendering is track.
@for (product of products;track product.id) {
The track expression tells Angular how to uniquely identify each item.
This is extremely important for performance.
Why Is Tracking Important?
With track product.id, Angular can identify each product using its unique ID.
Before101 -> Product A102 -> Product B103 -> Product CAfter101 -> Product A102 -> Product B103 -> Product C104 -> Product DAngular can determine:101 -> Existing102 -> Existing103 -> Existing104 -> New
This helps Angular reuse existing DOM elements efficiently.
Real-World Tracking Example
Imagine a stock trading application displaying 5,000 stocks with prices updating continuously.
A stable identifier such as symbol can be used:
@for (stock of stocks;track stock.symbol) {<app-stock-row[stock]="stock" />}
Angular can associate each rendered row with the corresponding stock.
This makes UI updates more predictable and efficient.
Choosing the Correct Tracking Key
Whenever possible, use a unique and stable identifier.
track product.idtrack user.idtrack transaction.transactionId
Potentially acceptable for truly static primitive values:
track product
Be careful with:
track $index
Using the index can be appropriate for collections that are completely static and never reordered. If items are inserted, deleted, sorted, or reordered, a stable unique ID is generally a better choice.
Contextual Variables
Angular's @for provides useful contextual variables.
$index$first$last$even$odd$count
@for (product of products;track product.id;let position = $index) {<p>{{ position + 1 }}.{{ product.name }}</p>}
Output:
1. Laptop2. Mobile3. Tablet
Using $first
@for (product of products;track product.id;let firstItem = $first) {@if (firstItem) {<span>Featured</span>}<p>{{ product.name }}</p>}
Using $last
@for (product of products;track product.id;let lastItem = $last) {<p>{{ product.name }}</p>@if (!lastItem) {<hr>}}
This prevents a separator from appearing after the final item.
Using $even and $odd
@for (transaction of transactions;track transaction.id;let evenRow = $even) {<div[class.alternate-row]="evenRow">{{ transaction.description }}</div>}
For simple visual striping, CSS selectors such as :nth-child() may sometimes be cleaner.
Using $count
@for (product of products;track product.id;let total = $count) {<p>{{ product.name }}</p>@if ($first) {<p>Total Products: {{ total }}</p>}}
Handling Empty Lists with @empty
Modern Angular provides an elegant way to handle empty collections.
@for (product of products;track product.id) {<app-product-card[product]="product" />} @empty {<p>No products found.</p>}
Instead of writing a separate condition, you can handle the empty state directly with the loop.
Real-World Search Example
Imagine the user searches for Quantum Computer XYZ and no products are found.
Without an empty state, the user sees a blank screen.
A better experience is:
No products matched your search.Try changing your filters or search term.
Search API│▼Product Array│▼@for│├── Has Items -> Product Cards│└── Empty -> Empty State
Traditional List Rendering with *ngFor
Existing Angular applications commonly use *ngFor.
<div*ngFor="let product of products">{{ product.name }}</div>
For performance optimization, traditional *ngFor often uses a trackBy function.
trackByProductId(index: number,product: Product): number {return product.id;}
<div*ngFor="let product of products;trackBy: trackByProductId">{{ product.name }}</div>
Modern Angular simplifies this with @for (product of products; track product.id).
@for vs *ngFor
| Feature | @for | *ngFor |
|---|---|---|
| Type | Built-in control flow | Structural directive |
| Modern Angular | Preferred for many new projects | Common in existing projects |
| Tracking | track item.id | trackBy function |
| Empty state | @empty | Separate condition/template |
| Syntax | Control-flow style | Microsyntax |
When maintaining enterprise applications, you'll likely encounter both.
Combining @for and @if
Lists frequently contain conditional content.
@for (product of products;track product.id) {<div class="product-card"><h3>{{ product.name }}</h3>@if (product.stock > 10) {<p>In Stock</p>} @else if (product.stock > 0) {<p>Only {{ product.stock }} left</p>} @else {<p>Out of Stock</p>}</div>}
This creates a dynamic UI for every product.
Component-Based List Architecture
In professional applications, avoid putting very large item templates directly inside the loop. Instead, create reusable components.
@for (product of products;track product.id) {<app-product-card[product]="product"(addToCart)="handleAddToCart($event)" />}
This provides better reusability, easier testing, cleaner templates, better separation of concerns, and easier maintenance.
Real-World Enterprise Architecture
Consider an order management system.
Backend API│▼Order Service│▼Order List Component│▼@for│├── Order Row├── Order Row└── Order Row
The list component handles data loading, pagination, filtering, and sorting. The item component handles order display, row-specific interactions, and status presentation.
Handling Large Lists
Suppose your backend contains 1,000,000 customer records.
The Angular application should not download and render all one million records.
Professional applications use strategies such as:
PaginationServer-Side FilteringServer-Side SortingVirtual ScrollingInfinite ScrollingLazy Data Loading
Strategy 1: Pagination
Pagination divides data into pages.
User Requests Page│▼Angular Component│▼Service│▼Backend API│▼50 Records│▼Angular List
This dramatically reduces the amount of data transferred and rendered at one time.
Server-Side Pagination Example
The frontend may request:
GET /api/products?page=1&size=20
The backend may return:
{"content": [],"page": 1,"size": 20,"totalElements": 10000,"totalPages": 500}
Angular renders only 20 products instead of 10,000 products.
Strategy 2: Infinite Scrolling
Social media applications often load more data as the user scrolls.
Initial Load│▼20 Items│▼User Scrolls│▼Load More│▼20 Additional Items
This works well for social feeds, product discovery, news feeds, and image galleries. Implement it carefully for accessibility, navigation, and memory management.
Strategy 3: Virtual Scrolling
Virtual scrolling is useful when the application needs to work with a large collection while rendering only the visible portion.
Total Dataset10,000 Items│▼Virtual Scroll Viewport│▼Visible Range│▼~20-40 DOM Elements
Angular applications can use Angular CDK virtual scrolling capabilities for appropriate use cases.
Pagination vs Infinite Scroll vs Virtual Scroll
| Strategy | Best For | Main Benefit |
|---|---|---|
| Pagination | Tables, admin systems | Predictable navigation |
| Infinite Scroll | Feeds, discovery | Continuous browsing |
| Virtual Scroll | Large visible collections | Fewer DOM nodes |
| Server Pagination | Huge datasets | Reduced network and memory usage |
The correct solution depends on the product requirements.
Real-World Case Study: 100,000 Transactions
A poor architecture would fetch and render all 100,000 records. A better architecture sends page, filter, and sort criteria to the backend and renders only the current page.
User│▼Angular Table│├── Page├── Filter└── Sort│▼API Request│▼Backend│▼Database Query│▼50 Records│▼Angular│▼Render 50 Rows
Filtering Lists
For small local collections, filtering can happen in the application. For very large datasets, filtering should usually happen on the server.
GET /api/products?category=laptop
The backend returns only matching results.
Sorting Lists
The same principle applies to sorting.
Small dataset: Client-Side SortingLarge dataset: Server-Side SortingGET /api/products?sort=price,asc
This avoids unnecessary processing in the browser.
List Rendering and Signals
Modern Angular applications can use Signals to manage reactive collections.
import {Component,signal} from '@angular/core';@Component({selector: 'app-product-list',templateUrl: './product-list.component.html'})export class ProductListComponent {products = signal([{ id: 1, name: 'Laptop' },{ id: 2, name: 'Phone' }]);}
@for (product of products();track product.id) {<p>{{ product.name }}</p>}
When the Signal changes, Angular can update the relevant rendered list.
Performance Optimization Checklist
- Does every item have a stable unique ID?
- Am I rendering too many DOM elements?
- Am I downloading too much data?
- Am I filtering millions of records in the browser?
- Is the item template too complex?
- Are expensive calculations running repeatedly?
Best Practices
- Use modern
@forfor new Angular applications where appropriate. - Understand
*ngForfor maintaining existing projects. - Always provide a meaningful
trackexpression. - Prefer stable unique IDs for dynamic collections.
- Use
@emptyto provide a useful empty state. - Keep list item templates focused and readable.
- Extract complex items into reusable components.
- Avoid rendering thousands of DOM elements unnecessarily.
- Use server-side pagination for large datasets.
- Use virtual scrolling when large collections need smooth scrolling.
- Perform large-scale filtering and sorting on the backend.
- Measure performance before introducing unnecessary complexity.
Common Pitfalls
Not Tracking Items Properly
Poor tracking can cause unnecessary DOM work. Prefer track product.id when a stable ID is available.
Using $index for Dynamic Lists
If items can be reordered or inserted, index-based tracking may not represent stable item identity.
Rendering Huge Datasets
Avoid loading and rendering 100,000 records directly into the DOM.
Ignoring Empty States
@empty {<p>No records found.</p>}
Performing Heavy Logic Inside Loops
@for (product of products; track product.id) {{{ calculateComplexPrice(product) }}}
If the calculation is expensive and frequently evaluated, calculate derived state elsewhere.
Making One Giant List Component
Separate API calls, pagination, filtering, sorting, row rendering, dialogs, and business logic where appropriate.
Common Misconceptions
Misconception
@for is simply a JavaScript for loop.
Reality
@for is Angular's template control-flow mechanism for rendering collections and managing corresponding views.
Misconception
Tracking is optional performance decoration.
Reality
Correct item identity is fundamental to efficient list updates. Modern @for explicitly requires a tracking strategy.
Misconception
Virtual scrolling loads only visible records from the database.
Reality
Virtual scrolling primarily controls how many DOM elements are rendered. Data loading and backend pagination are separate architectural concerns, although they can be combined.
Misconception
Client-side filtering is always faster.
Reality
For large datasets, transferring everything to the browser is usually inefficient. Server-side filtering and pagination are often more scalable.
Advanced interview questions
Interview Prep
Practice concise answers, then expand each card for the explanation.
1BeginnerQuestionHow do you render a list in modern Angular?+
Answer
@for control-flow block, for example @for (product of products; track product.id).2BeginnerQuestionWhy is track important?+
Answer
track provides stable identity for list items, allowing Angular to efficiently associate collection items with rendered DOM elements when the collection changes.3IntermediateQuestionWhat should you use as a tracking key?+
Answer
product.id, user.id, or transaction.id.4BeginnerQuestionWhat is @empty?+
Answer
@empty defines the content Angular should render when the collection processed by @for contains no items.5IntermediateQuestionWhat is the difference between @for and *ngFor?+
Answer
@for is modern Angular's built-in control-flow syntax. *ngFor is the traditional structural directive commonly found in existing Angular applications.6AdvancedQuestionHow would you display 100,000 records in Angular?+
Answer
7IntermediateQuestionWhat is virtual scrolling?+
Answer
8IntermediateQuestionWhat is the difference between pagination and virtual scrolling?+
Answer
9AdvancedQuestionShould filtering happen in Angular or the backend?+
Answer
10AdvancedQuestionHow would you architect a large enterprise data table?+
Answer
Summary
Angular Lists allow applications to transform collections of data into dynamic user interfaces.
At a basic level:
Array│▼@for│▼Rendered Items
But enterprise applications require a deeper architectural approach.
Database│▼Backend API│▼Pagination / Filter / Sort│▼Angular Service│▼List Component│▼@for│▼track stable item ID│▼Item Components│▼Efficient DOM
The most important lesson is that list rendering isn't simply about looping over an array.
When applications handle thousands or millions of records, developers must think about stable item identity, DOM size, network payload, pagination, filtering, sorting, virtual scrolling, and component architecture.
Mastering these concepts will help you build Angular applications that remain fast and maintainable as datasets grow.
In the next lesson, Angular Forms, you'll learn how Angular captures and validates user input, understand Template-Driven and Reactive Forms, build real-world forms, handle validation and error messages, and explore the form architecture commonly used in enterprise Angular applications.