DataGrid virtual scrolling
Browse a large loaded list while the grid renders only the rows near the viewport.
Available in v2.5.0. Choose local data, application-managed remote windows, or grouped data.
Browse 10,000 inventory records
The following example demonstrates a 384px viewport with fixed 48px rows. Scroll, sort a column, search for a SKU or jump to a record. The full array stays in memory while only a small window is rendered.
10,000 records loaded. Try searching for SKU-05000.
Enable virtual scrolling
Import Datagrid and supply stable row keys, explicit column widths and a fixed row height. The height includes the cell border. Cell templates must fit inside it; overflowing content is clipped.
<ngb-datagrid [data]="inventory" [columns]="columns" [trackBy]="trackRow"
scrollable="virtual" [height]="384"
[virtualRowHeight]="48" [virtualOverscan]="5"
[enablePagination]="false" [enableSorting]="true" />
virtualOverscan controls extra rows around the viewport (default 5, maximum 100). A larger buffer renders more cells. Local sorting, filtering, paging or replacing data resets the scroll position. Remote window responses preserve it; changing the remote query requests the first range.
Data and performance
Virtualization reduces DOM work. It does not reduce the size of your array, network response or the cost of local sorting and filtering. Keep templates inexpensive and measure with your own data and devices.
Pagination can be combined with virtualization: only the current page is virtualized. With ordinary server paging, the grid virtualizes the loaded page. Enable virtualRemote for scrolling across an entire remote result: range events request unloaded records, and placeholders reserve their space. Scrolling emits virtualRangeChange, not dataStateChange.
Load remote windows
The following example demonstrates a simulated server with 10,000 shipments and a 350ms response delay. Scroll or jump to shipment 8,000: only the requested window is returned. Sorting and searching run on the simulated server.
Waiting for the first range.
Bind data, virtualSkip and total from the same response. The total is the count after filtering. The grid emits virtualRangeChange after the debounce delay; use its skip, take and state to query your API.
<ngb-datagrid [data]="data()" [columns]="columns" [trackBy]="trackRow"
scrollable="virtual" [height]="288" [virtualRemote]="true"
[virtualSkip]="skip()" [total]="total()" [virtualPageSize]="60"
[virtualDebounce]="100" [loading]="loading()"
[enablePagination]="false" (virtualRangeChange)="requests.next($event)" />
// Import Subject, switchMap, map, catchError and EMPTY from rxjs.
// Result is your API response: { data: Shipment[]; total: number }.
// Apply takeUntilDestroyed() when subscribing inside an Angular component.
requests.pipe(
switchMap(range => http.get<Result>('/api/shipments', {
params: { skip: range.skip, take: range.take,
state: JSON.stringify(range.state) }
}).pipe(
map(result => ({ range, result })),
catchError(error => { showError(error); return EMPTY; })
))
).subscribe(({ range, result }) => {
skip.set(range.skip);
data.set(result.data);
total.set(result.total);
});
Use switchMap to cancel obsolete HTTP subscriptions, or reject stale responses with a request ID. Handle errors inside the inner observable so later requests still work. Cache by range and sort/filter state. The grid does not choose an endpoint or persist a cache. Retry with requestVirtualRange(true). Listen to the range output for fetching; also fetching from dataStateChange would duplicate requests.
Groups and expansion
The following example demonstrates shipments grouped by depot. Headers and data rows share the configured row height. Collapse a group to remove its records from the virtual scroll range.
Shipment | Depot | Destination | Cartons |
|---|
<ngb-datagrid #grid [data]="rows" [columns]="columns"
scrollable="virtual" [height]="288" [groupable]="true"
[group]="[{ field: 'depot' }]" [enablePagination]="false" />
<button (click)="grid.collapseGroup(0)">Collapse first group</button>
For nested groups, pass a sibling-index path such as collapseGroup([0, 1]). A server can provide a complete groupedData tree instead of local grouping. Use virtualRemote=false for complete trees: remote flat windows cannot describe unloaded group geometry. Group templates and aggregate footers must fit virtualRowHeight.
Paging, details and responsive content
The following example demonstrates virtual rows inside a 100-record page. Expand a shipment for a 120px detail area. Resize the page to see the destination template switch to a shorter label without changing row height.
Shipment | Depot | Destination | Cartons |
|---|
<ngb-datagrid [data]="rows" [columns]="columns"
scrollable="virtual" [height]="288" [detailRowHeight]="120"
[enablePagination]="true" [pageSize]="100">
<ng-template ngbRowDetail let-row>
<p>Shipment {{ row.id }}: {{ row.cartons }} cartons</p>
</ng-template>
</ngb-datagrid>
Paging virtualizes only the active page. Detail content scrolls inside detailRowHeight; expanding a row updates the offsets. Responsive templates may change their content, but must keep the fixed height. Card/stacked row layouts use regular rendering.
Keyboard and accessibility
Tab enters a rendered data cell. Arrow keys move between cells; Ctrl+Home and Ctrl+End reach the first and last row in the current page. Moving focus beyond the window renders the destination before focusing it.
The grid exposes logical row counts and indexes. Spacer rows are hidden from assistive technology. Browser Find and screen-reader browse mode can only inspect rendered rows; use grid search or regular scrolling when all rows must be available in the DOM.
Supported combinations and limitations
Supported: local sorting/filtering, remote windows, grouping, fixed-height details, row selection, pinned columns, pagination, themes and external-dialog editing.
Sticky rows, row reordering, responsive cards, stacked layouts, add rows, batch editing and inline/in-cell/toolbar editing use regular rendering instead, with a visible explanation. Remote windows require flat rows; grouped results need a complete tree. Variable row heights and column virtualization are not supported. Very large row counts can exceed browser scroll-height limits: reduce the range with filtering or paging.
Use regular scrolling for those workflows. Scroll mode, viewport size and current scroll position are not part of saved views or Undo/Redo history.