Angular Tutorial 0/42 lessons ~6 min read Lesson 13

    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.

    Course progress0%
    Focus
    39 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    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 @for control flow works.
    • How to use the track expression correctly.
    • How to access $index, $first, $last, $even, and $odd.
    • How to handle empty collections using @empty.
    • How traditional *ngFor works.
    • 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:

    text
    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.

    text
    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:

    text
    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:

    text
    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:

    typescript
    products = [
    'Laptop',
    'Mobile',
    'Tablet',
    'Headphones'
    ];

    Instead of manually writing repeated HTML, you can use Angular's modern @for control flow:

    html
    @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

    1
    Backend API
    2
    Service
    3
    Component
    4
    Product Array
    5
    @for
    6
    Item Views
    7
    DOM Nodes
    8
    Browser UI

    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:

    html
    @for (item of items; track item) {
    <!-- Render item -->
    }

    Example:

    typescript
    export class ProductComponent {
    products = [
    'Laptop',
    'Mobile',
    'Tablet'
    ];
    }
    html
    @for (product of products; track product) {
    <p>
    {{ product }}
    </p>
    }

    Output:

    text
    Laptop
    Mobile
    Tablet

    Rendering Objects

    Real applications rarely work with simple string arrays. Usually, the backend returns objects.

    typescript
    products = [
    { id: 101, name: 'Laptop', price: 75000 },
    { id: 102, name: 'Mobile', price: 45000 },
    { id: 103, name: 'Tablet', price: 30000 }
    ];
    html
    @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.

    html
    @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.

    text
    Before
    101 -> Product A
    102 -> Product B
    103 -> Product C
    After
    101 -> Product A
    102 -> Product B
    103 -> Product C
    104 -> Product D
    Angular can determine:
    101 -> Existing
    102 -> Existing
    103 -> Existing
    104 -> 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:

    html
    @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.

    html
    track product.id
    track user.id
    track transaction.transactionId

    Potentially acceptable for truly static primitive values:

    html
    track product

    Be careful with:

    html
    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.

    text
    $index
    $first
    $last
    $even
    $odd
    $count
    html
    @for (
    product of products;
    track product.id;
    let position = $index
    ) {
    <p>
    {{ position + 1 }}.
    {{ product.name }}
    </p>
    }

    Output:

    text
    1. Laptop
    2. Mobile
    3. Tablet

    Using $first

    html
    @for (
    product of products;
    track product.id;
    let firstItem = $first
    ) {
    @if (firstItem) {
    <span>Featured</span>
    }
    <p>{{ product.name }}</p>
    }

    Using $last

    html
    @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

    html
    @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

    html
    @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.

    html
    @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:

    text
    No products matched your search.
    Try changing your filters or search term.
    text
    Search API
    Product Array
    @for
    ├── Has Items -> Product Cards
    └── Empty -> Empty State

    Traditional List Rendering with *ngFor

    Existing Angular applications commonly use *ngFor.

    html
    <div
    *ngFor="let product of products">
    {{ product.name }}
    </div>

    For performance optimization, traditional *ngFor often uses a trackBy function.

    typescript
    trackByProductId(
    index: number,
    product: Product
    ): number {
    return product.id;
    }
    html
    <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
    TypeBuilt-in control flowStructural directive
    Modern AngularPreferred for many new projectsCommon in existing projects
    Trackingtrack item.idtrackBy function
    Empty state@emptySeparate condition/template
    SyntaxControl-flow styleMicrosyntax

    When maintaining enterprise applications, you'll likely encounter both.

    Combining @for and @if

    Lists frequently contain conditional content.

    html
    @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.

    html
    @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.

    text
    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:

    text
    Pagination
    Server-Side Filtering
    Server-Side Sorting
    Virtual Scrolling
    Infinite Scrolling
    Lazy Data Loading

    Strategy 1: Pagination

    Pagination divides data into pages.

    text
    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:

    text
    GET /api/products?page=1&size=20

    The backend may return:

    json
    {
    "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.

    text
    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.

    text
    Total Dataset
    10,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

    StrategyBest ForMain Benefit
    PaginationTables, admin systemsPredictable navigation
    Infinite ScrollFeeds, discoveryContinuous browsing
    Virtual ScrollLarge visible collectionsFewer DOM nodes
    Server PaginationHuge datasetsReduced 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.

    text
    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.

    text
    GET /api/products?category=laptop

    The backend returns only matching results.

    Sorting Lists

    The same principle applies to sorting.

    text
    Small dataset: Client-Side Sorting
    Large dataset: Server-Side Sorting
    GET /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.

    typescript
    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' }
    ]);
    }
    html
    @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

    1. Does every item have a stable unique ID?
    2. Am I rendering too many DOM elements?
    3. Am I downloading too much data?
    4. Am I filtering millions of records in the browser?
    5. Is the item template too complex?
    6. Are expensive calculations running repeatedly?

    Best Practices

    • Use modern @for for new Angular applications where appropriate.
    • Understand *ngFor for maintaining existing projects.
    • Always provide a meaningful track expression.
    • Prefer stable unique IDs for dynamic collections.
    • Use @empty to 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

    html
    @empty {
    <p>No records found.</p>
    }

    Performing Heavy Logic Inside Loops

    html
    @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.

    10 questions
    1BeginnerQuestionHow do you render a list in modern Angular?+

    Answer

    Use the @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

    Prefer a unique and stable identifier such as 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

    Avoid loading and rendering all records simultaneously. Consider server-side pagination, filtering, sorting, virtual scrolling, and incremental loading.
    7IntermediateQuestionWhat is virtual scrolling?+

    Answer

    Virtual scrolling renders only the portion of a large collection currently visible to the user, plus a small buffer, instead of creating DOM elements for the entire collection.
    8IntermediateQuestionWhat is the difference between pagination and virtual scrolling?+

    Answer

    Pagination divides data into pages. Virtual scrolling manages the rendered viewport so only visible items are represented in the DOM.
    9AdvancedQuestionShould filtering happen in Angular or the backend?+

    Answer

    For small collections already loaded in memory, client-side filtering can be appropriate. For large datasets, server-side filtering is generally more scalable.
    10AdvancedQuestionHow would you architect a large enterprise data table?+

    Answer

    Use Angular table state for pagination, sorting, filtering, and search, delegate data access to an Angular service, and let the backend perform large-scale pagination, filtering, and sorting.

    Summary

    Angular Lists allow applications to transform collections of data into dynamic user interfaces.

    At a basic level:

    text
    Array
    @for
    Rendered Items

    But enterprise applications require a deeper architectural approach.

    text
    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.

    Ready to mark this lesson complete?Track your journey across the entire course.