Close
Angular React Web Components Blazor Angular
Open Source

Virtual Scroll Component

The Ignite UI for Angular Virtual Scroll is a component that renders large lists by keeping only the items in its viewport, plus a configurable buffer, in the DOM. The scrollbar still spans the whole collection, so a virtual list of a hundred thousand items scrolls like a regular list.

Live Demo

Anatomy

The Angular Virtual Scroll renders the visible items plus a configurable buffer, and its track preserves the scroll range of the whole collection.

Angular Virtual Scroll anatomy: host, track, content element, item wrappers, and over-scan buffers
1. Host: The scroll container. Its fixed height (width when horizontal) sets how many items are visible.
2. Track: A spacer sized to the estimated length of the whole collection, so the scrollbar spans every item.
3. Content element: Holds only the rendered items. It starts at the first rendered buffer item, above the viewport, and takes its size from the rendered items.
4. Item wrapper: One per rendered item. It hosts the item template and is the box that gets measured.
5. Over-scan buffer: The overScan items (2 by default) rendered past each edge of the viewport.
igx-virtual-scroll                      — scrollable viewport (role="list")
└── .igx-virtual-scroll__track          — provides the collection's scroll range
    └── .igx-virtual-scroll__content    — positions the rendered window
        └── .igx-virtual-item           — one wrapper per rendered item (data-index); hosts the item template

Getting Started

Set up Ignite UI for Angular with the Getting Started topic, then import the VirtualScroll and the IgxVirtualItemDirective, which marks the item template:

import { Component } from '@angular/core';
import { IgxVirtualItemDirective, IgxVirtualScrollComponent } from 'igniteui-angular/virtual-scroll';

@Component({
    selector: 'app-employees',
    imports: [IgxVirtualScrollComponent, IgxVirtualItemDirective],
    templateUrl: './employees.component.html'
})
export class EmployeesComponent {
    public items = Array.from({ length: 100_000 }, (_, i) => ({ name: `Item ${i}` }));
}
<igx-virtual-scroll [data]="items" style="height: 400px">
    <ng-template igxVirtualItem let-item let-index="index">
        <div class="row">{{ index }}: {{ item.name }}</div>
    </ng-template>
</igx-virtual-scroll>

The Virtual Scroll host needs a fixed height for vertical scrolling or a fixed width for horizontal scrolling. A host that grows with its content renders every item, so the list is not virtualized.

Prerequisites and Version Compatibility

Requirement Value
Package igniteui-angular (MIT)
Entry point igniteui-angular/virtual-scroll
First release with the component 22.2.0

Usage

Item Template

The Virtual Scroll item template receives the item and its position in the whole collection. Use the index and the total count for position-dependent content, such as alternating styles or aria-posinset and aria-setsize.

Mark an ng-template with igxVirtualItem, or pass a template defined elsewhere through itemTemplate, which takes precedence. The template context provides $implicit (the item), index, count, first, last, even, and odd.

<igx-list>
    <igx-virtual-scroll role="presentation" [data]="employees" [estimatedItemSize]="64" style="height: 480px">
        <ng-template igxVirtualItem let-employee let-index="index" let-count="count">
            <igx-list-item [attr.aria-posinset]="index + 1" [attr.aria-setsize]="count">
                <igx-avatar igxListThumbnail shape="circle" [initials]="employee.initials"></igx-avatar>
                <span igxListLineTitle>{{ employee.name }}</span>
                <span igxListLineSubTitle>{{ employee.email }}</span>
            </igx-list-item>
        </ng-template>
    </igx-virtual-scroll>
</igx-list>

Data

The Virtual Scroll data collection is compared by reference. Assign a new array to update the list; changing the bound array in place, for example with push, does not update it.

this.employees = [...this.employees, newEmployee];

When data changes, the component keeps the measured sizes of the items before the first changed index and measures the rest again when they render. Appending keeps every existing measurement; replacing, filtering, or sorting discards the measurements from the first changed item onwards.

Estimated Item Size

The Virtual Scroll estimatedItemSize is the size in pixels an item has until it renders and is measured (50 by default). Items can have different sizes: each measured size replaces the estimate.

Set the estimate close to the average item size to keep the scrollbar and scrollToIndex accurate before items are measured.

<igx-virtual-scroll [data]="employees" [estimatedItemSize]="80" style="height: 480px">...</igx-virtual-scroll>

Items are measured by their border box, so margins are not part of an item’s size. Space items with padding, or with a gap inside the item, instead of margins.

Orientation

The Virtual Scroll orientation sets the scroll axis: vertical (default) or horizontal. In a horizontal list, give each item a width and the host a height. In a right-to-left context, horizontal scrolling and item positioning are mirrored.

<igx-virtual-scroll orientation="horizontal" [data]="employees" [estimatedItemSize]="220" style="height: 200px">
    <ng-template igxVirtualItem let-employee>
        <div style="width: 220px">...</div>
    </ng-template>
</igx-virtual-scroll>

Over-Scan

The Virtual Scroll overScan is the number of extra items rendered beyond each edge of the viewport (2 by default). A larger value reduces blank areas during fast scrolling and renders more elements.

<igx-virtual-scroll [data]="items" [overScan]="6" style="height: 400px">...</igx-virtual-scroll>

Scroll to Index

The Virtual Scroll scrollToIndex method scrolls an item into view. Its options are those of the native scrollIntoView: block (start, center, end, or nearest), inline for a horizontal list, and behavior (auto or smooth). Items that have not rendered only have an estimated size, so the component measures the items where it lands and corrects the position; the returned promise resolves on the final position.

private readonly virtualScroll = viewChild.required(IgxVirtualScrollComponent);

public async goTo(index: number): Promise<void> {
    await this.virtualScroll().scrollToIndex(index, { block: 'center' });
}

With block: 'nearest', the position does not change when the item is already fully visible. Indices outside the collection are clamped to the first or last item.

Infinite Scroll

The Virtual Scroll dataRequest output supports append-only loading from remote data. It is emitted when the rendered window nears the end of data, and on the first render when the loaded items do not fill the viewport. Append the requested items as a new array:

<igx-virtual-scroll [data]="employees()" [estimatedItemSize]="64" (dataRequest)="loadMore($event)" style="height: 440px">
    <ng-template igxVirtualItem let-employee>...</ng-template>
</igx-virtual-scroll>
public readonly employees = signal<Employee[]>(firstPage);

public loadMore(request: VirtualScrollDataRequest): void {
    this.service.fetch(request.startIndex, request.count).subscribe(page => {
        this.employees.update(current => [...current, ...page]);
    });
}

Only one data request is pending at a time; the next one follows the next data change. An empty data emits no request, so load the first page yourself. When the source has no more items, stop appending: the component does not request the same start index again.

Paged Data

The Angular Virtual Scroll dataWindow input binds a page of a larger collection instead of data. The list is as long as totalCount, so the scrollbar spans the whole collection while only the page is in memory, and indices that the page does not cover render nothing.

interface VirtualDataWindow<T> {
    readonly items: readonly T[]; // the loaded page
    readonly startIndex: number;  // the index of items[0] in the whole collection
    readonly totalCount: number;  // the size of the whole collection
}

Load the next page from the range that stateChange reports. Cancel the previous request, so a slow response cannot replace a newer page:

<igx-virtual-scroll [dataWindow]="page()" [estimatedItemSize]="64" (stateChange)="onStateChange($event)" style="height: 440px">
    <ng-template igxVirtualItem let-employee>...</ng-template>
</igx-virtual-scroll>
public readonly page = signal<VirtualDataWindow<Employee>>({ items: [], startIndex: 0, totalCount: 100_000 });
private pending?: Subscription;

public onStateChange(state: VirtualScrollState): void {
    const page = this.page();
    if (state.startIndex >= page.startIndex && state.endIndex < page.startIndex + page.items.length) {
        return; // The loaded page already covers the range.
    }

    const startIndex = Math.max(0, state.startIndex - 30);
    const count = state.endIndex + 30 - startIndex + 1;

    this.pending?.unsubscribe();
    this.pending = this.service.fetch(startIndex, count).subscribe(result => {
        this.page.set({ items: result.items, startIndex, totalCount: result.total });
    });
}

Measured sizes are kept per index while totalCount stays the same; a page with a different totalCount, such as a filtered result, is measured again. dataRequest is not emitted while dataWindow is bound. The component stores one size entry per index, so its memory grows with totalCount: roughly 17 MB for a million items.

Layout Complete

The Virtual Scroll layoutComplete property is a promise that resolves when the current render, the measurements it triggers, and the renders they schedule are complete. Await it before you read rendered items after a data change, a scroll, or a resize.

this.employees = await firstValueFrom(this.service.fetchAll());
await this.virtualScroll().layoutComplete;

Do/Don’t

The Virtual Scroll usually works as the scroll container of a long list, keeping only the items in its viewport, plus a small buffer, in the DOM. Avoid it for a list short enough to render at once, and as you write the item template, keep each item state in the data rather than in its elements: item elements are reused, so DOM state the template does not bind shows on whichever item takes the element.

Angular Virtual Scroll showing a list of 100,000 employees
Do

Use the Virtual Scroll for a long list that is too large to render at once, such as a directory, a feed, a log, or a strip of cards, including lists that load remote data while scrolling.

Angular Virtual Scroll used for a list of only five employees
Don’t

Render a short list directly with the List and @for. Use the Angular Data Grid for tabular data with columns, sorting, or filtering. Show a small set of rich items as Card elements without virtualization.

Properties

Name Type Default Description
data T[] [] The collection to virtualize. Compared by reference.
dataWindow VirtualDataWindow<T> | null null A page of a larger collection, used instead of data while it is set.
orientation 'vertical' | 'horizontal' 'vertical' The scroll axis.
overScan number 2 Extra items rendered beyond each edge of the viewport.
estimatedItemSize number 50 The size in pixels of an item until it is measured. A non-positive value uses 50.
itemTemplate TemplateRef<IgxVsItemContext<T>> | null null The item template. Takes precedence over a projected ng-template[igxVirtualItem].
layoutComplete Promise<void> (read-only) — Resolves when rendering and item measurement have settled.

Methods

Name Returns Description
scrollToIndex(index: number, options?: ScrollIntoViewOptions) Promise<void> Scrolls the item at index into view and resolves when the corrected position is stable.

Events

Name Payload Description
stateChange VirtualScrollState Emitted when the rendered window changes: startIndex, endIndex, viewportSize, totalSize.
dataRequest VirtualScrollDataRequest Emitted when the rendered window nears the end of data: startIndex, count. Not emitted while dataWindow is bound.

Styling

The Angular Virtual Scroll has no theme of its own: it lays out the viewport, and the rendered items take their styles from the elements and components in the item template.

Size the host and target the rendered items with the classes from the Anatomy:

.employees igx-virtual-scroll {
    block-size: 480px;
}

.employees .igx-virtual-item:nth-child(even) {
    background: var(--ig-gray-100);
}

Accessibility

The Angular Virtual Scroll keeps only the rendered window in the DOM, so the item template has to expose each item’s position in the whole collection.

Keyboard Interaction

The Angular Virtual Scroll adds no key handlers. The host is a native scroll container, and a focused scroll container scrolls with the browser’s keys:

Key Action
Arrow Up / Arrow Down Scrolls a vertical list.
Arrow Left / Arrow Right Scrolls a horizontal list.
Page Up / Page Down Scrolls by about one viewport.
Home / End Scrolls to the start or the end of the collection.

The host has no tabindex. Browsers differ in whether a scroll container without focusable content can receive focus, so set tabindex="0" on the host when the items contain nothing focusable. Focus inside an item does not survive that item leaving the rendered window, so move focus deliberately before it does.

Screen Readers / ARIA

  • The host has role="list"; the track, the content element, and the item wrappers have role="presentation". Items that render role="listitem", such as igx-list-item, are exposed as items of that list.
  • Inside a container that already provides list semantics, such as igx-list, set role="presentation" on the host so that the items are not nested in a second list.
  • Map the index and count template variables to aria-posinset and aria-setsize.
  • Give a focusable host an accessible name with aria-label or aria-labelledby.

Accessibility Compliance

Infragistics documents the accessibility standards that Ignite UI for Angular targets in the Accessibility Compliance topic. This topic makes no conformance claim for the Virtual Scroll: the table lists what the component provides, and the list after it covers what the application must add.

Criterion How the component supports the requirement
1.3.1 Info and Relationships The wrappers are presentational, so the list structure comes from the host and the item template, which can expose each item’s position with aria-posinset and aria-setsize.
2.1.1 Keyboard The host is a native scroll container that scrolls with the keyboard once it has focus. Reaching it with the keyboard depends on the application; see the list below.

Your responsibilities:

  • Make the host keyboard-reachable with tabindex="0" when the items contain nothing focusable, and give it an accessible name.
  • Expose the item position with aria-posinset and aria-setsize from the item template.
  • Provide list semantics that fit the item template (see Screen Readers / ARIA).
  • Keep application state, such as a selection, in the data rather than in the rendered item elements.

Troubleshooting

Why does the Virtual Scroll render no items?

The host has no size on the scroll axis, the item template is missing, or data is empty. Give the host a fixed height (vertical) or width (horizontal), set the item template, and check the bound collection.

Why does the list not update when I add an item?

The Virtual Scroll compares data by reference, so a change in place is not detected. Assign a new array, for example [...items, newItem].

Why does the scrollbar change size while I scroll?

Items that have not rendered use estimatedItemSize, and the total size is corrected as items are measured.

Set estimatedItemSize close to the average item size.

Why do items drift out of place further down the list?

Margins are not part of an item’s measured size. Replace item margins with padding, or with a gap inside the item.

Why does a list inside a drop-down or dialog show its items one frame late?

A container that is hidden until it opens has no size in the change detection pass that reveals it, so the host is measured after that render and the items render in the next frame. Read the rendered items after layoutComplete resolves.

How do I replace an igxForOf list with the Virtual Scroll?

The Virtual Scroll measures items at runtime and creates its own scroll container, so the container size and scroll container inputs of igxForOf have no equivalent. The igxForOf directive is deprecated in favor of the Virtual Scroll; existing lists keep working, but use the Virtual Scroll for new lists that virtualize a single axis.

igxForOf Virtual Scroll
*igxFor="let item of data" [data]="data" with an ng-template igxVirtualItem
igxForScrollOrientation orientation
igxForContainerSize The host’s height or width, set with CSS
igxForItemSize estimatedItemSize (a starting estimate; items are measured)
igxForScrollContainer Not needed: the host is the scroll container
scrollTo(index) scrollToIndex(index, options), which returns a promise
chunkLoad, chunkPreload stateChange
igxForTotalItemCount for remote data dataWindow with totalCount, or data with dataRequest for append-only loading
index, count, first, last, even, odd The same template variables

The grids keep their own row and column virtualization; see Grid Virtualization.

Known Limitations

  • The Angular Virtual Scroll virtualizes one axis. Rows and columns that are both virtualized require a grid.

  • Item elements are reused as the window moves, so DOM state that the item template does not bind, such as a checkbox without a bound checked, shows on whichever item takes the element. Bind all item state, and write user changes back to the item.

  • With dataWindow, a page that keeps totalCount but places different records at the same indices keeps the measured sizes of the previous records until those rows render again.

API References

Dependencies

The Angular Virtual Scroll has no dependencies on other components. Import IgxVirtualScrollComponent and IgxVirtualItemDirective from igniteui-angular/virtual-scroll; the structural styles ship with the component.

Additional Resources

  • List - Use the List for a short list, or as the container of a virtualized list.
  • Data Grid - Use the Data Grid for tabular data with columns, sorting, or filtering.
  • Card - Use cards for a small set of rich items, or as items of a horizontal Virtual Scroll.
  • Virtual ForOf Directive - The directive-based virtualization used by existing lists.

FAQ

How many items can the Virtual Scroll handle?

The Angular Virtual Scroll keeps only the items in its viewport and the over-scan buffer in the DOM, so the size of the collection does not change how many elements render. When the total size of a collection exceeds the browser’s scroll limit, the Virtual Scroll maps the collection onto the scroll range the browser supports.

Do Virtual Scroll items need the same size?

Items in the Angular Virtual Scroll can have different sizes, because each item is measured once it renders. Set estimatedItemSize close to the average item size so that the scrollbar is accurate before items are measured.

How do I scroll the Virtual Scroll to a specific item?

Call the Angular Virtual Scroll scrollToIndex method with the item index and optional block and behavior options. The method returns a promise that resolves when the corrected position is stable.

How do I load remote data into the Virtual Scroll while the user scrolls?

The Angular Virtual Scroll supports two models. For append-only loading, handle dataRequest and assign a new array that includes the requested items. For a collection read a page at a time, bind dataWindow and load the range that stateChange reports.