A responsive virtual-scrolling grid for Angular with built-in infinite scroll. Uses CSS Grid for layout, auto-measures item dimensions, and only renders what's visible.
Angular CDK's cdk-virtual-scroll-viewport only handles single-column lists. If you need a responsive multi-column grid with virtual scrolling, you're on your own.
ngx-virtual-grid gives you a real CSS Grid that only renders visible items. You control the layout with standard grid-template-columns and gap - the library reads the computed grid to figure out column count and row height automatically. No config objects, no pixel math.
It also works as a single-column virtual list - just set grid-template-columns: 1fr.
- Virtual scrolling with CSS Grid layout
- Auto-measures item dimensions from the first rendered row
- Responsive - adapts to column count changes via CSS
- Infinite scroll with configurable threshold
- Works as a grid or a single-column list
- Works with both zoned and zoneless Angular apps
- SSR-safe with prerendering support
npm install @theryansmee/ngx-virtual-gridyarn add @theryansmee/ngx-virtual-gridpnpm add @theryansmee/ngx-virtual-gridEach Angular major version is maintained on its own branch:
| Branch | Angular | Library | npm tag |
|---|---|---|---|
angular/14 |
14.x | 14.x.x | angular14 |
angular/15 |
15.x | 15.x.x | angular15 |
angular/16 |
16.x | 16.x.x | angular16 |
angular/17 |
17.x | 17.x.x | angular17 |
angular/18 |
18.x | 18.x.x | angular18 |
angular/19 |
19.x | 19.x.x | angular19 |
angular/20 |
20.x | 20.x.x | angular20 |
angular/21 |
21.x | 21.x.x | angular21 |
angular/22 |
22.x | 22.x.x | latest |
The main branch tracks the latest stable version.
Import the component and directive directly (standalone):
import { Component } from '@angular/core';
import { NgxVirtualGridComponent, VirtualGridItemDirective } from '@theryansmee/ngx-virtual-grid';
@Component({
selector: 'app-example',
imports: [NgxVirtualGridComponent, VirtualGridItemDirective],
template: `
<ngx-virtual-grid
[items]="items"
[bufferSize]="3"
[loadMoreThreshold]="0.8"
(loadMore)="onLoadMore()">
<ng-template ngxVirtualGridItem let-item let-index="index">
<div class="card">{{ item.name }}</div>
</ng-template>
</ngx-virtual-grid>
`,
styles: [`
ngx-virtual-grid {
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 16px;
}
`],
})
export class ExampleComponent {
items: any[] = [];
onLoadMore(): void {
// Load more items...
}
}The layout is controlled entirely by CSS. The component is a CSS Grid container, so you just set grid-template-columns on it like you would any grid.
Responsive multi-column grid:
ngx-virtual-grid {
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 16px;
}Fixed 3-column grid:
ngx-virtual-grid {
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}Single-column list:
ngx-virtual-grid {
grid-template-columns: 1fr;
gap: 8px;
}Same component, same API. The library figures out the column count and row height from your CSS automatically.
You don't need to wrap your entire page in this component. It works alongside other content - just put it wherever you need a virtual list or grid:
<h1>My Dashboard</h1>
<p>Some intro text, a navbar, whatever you want up here.</p>
<ngx-virtual-grid [items]="products" (loadMore)="loadMoreProducts()">
<ng-template ngxVirtualGridItem let-product>
<app-product-card [product]="product" />
</ng-template>
</ngx-virtual-grid>
<footer>Still works down here too.</footer>By default it listens for scroll events on window, so it just works as part of your normal page scroll. No need for a fixed-height wrapper or any special container setup.
If you do want to put it inside a scrollable container (like a panel or sidebar), pass the container element as scrollParent:
<div #scrollContainer style="height: 600px; overflow-y: auto;">
<ngx-virtual-grid [items]="items" [scrollParent]="scrollContainer">
<ng-template ngxVirtualGridItem let-item>
<div class="card">{{ item.name }}</div>
</ng-template>
</ngx-virtual-grid>
</div>| Input | Type | Default | Description |
|---|---|---|---|
items |
unknown[] |
[] |
Array of data items to render |
bufferSize |
number |
3 |
Number of extra rows to render above and below the viewport |
loadMoreThreshold |
number |
0.8 |
Scroll ratio (0-1) at which the loadMore event fires |
scrollParent |
HTMLElement | null |
null |
Custom scroll container. Uses window if null |
| Output | Type | Description |
|---|---|---|
loadMore |
void |
Emits when the scroll position crosses the loadMoreThreshold. Resets when the items array length changes. |
| Method | Description |
|---|---|
scrollToIndex(index: number) |
Scroll to bring the item at index into view |
scrollToOffset(px: number) |
Scroll to an absolute pixel offset within the grid |
refresh() |
Re-measure dimensions and recalculate layout |
The ngxVirtualGridItem template receives:
| Variable | Type | Description |
|---|---|---|
$implicit |
T |
The data item |
index |
number |
The item's index in the original array |
The library works with both zoned and zoneless Angular apps. In zoneless mode, the loadMore output emits from a raw scroll event listener. If your handler modifies component state, use signals so the view updates:
items = signal<Item[]>([]);
onLoadMore(): void {
// Signal write triggers change detection in zoneless mode
this.items.update(current => [...current, ...newItems]);
}- Node.js 22.22+
- pnpm 11+
- Angular 22.x
# Install dependencies
pnpm install
# Build the library
pnpm run build:lib
# Start the demo app (builds library first, then serves demo)
pnpm startThe demo app runs at http://localhost:4200/.
| Script | Description |
|---|---|
pnpm run build:lib |
Build the library for production |
pnpm run build:demo |
Build the demo application |
pnpm start |
Build library + serve demo app |
pnpm test |
Run library unit tests (watch mode) |
pnpm run test:ci |
Run library unit tests (single run) |
pnpm run lint |
Lint all projects |
pnpm run lint:fix |
Lint and auto-fix all projects |
pnpm run build:lib
cd dist/ngx-virtual-grid
pnpm publishWhen publishing older Angular version branches, use the version-specific tag so it doesn't become latest:
pnpm publish --tag angular21- Branch off the appropriate
angular/*branch for your target Angular version - Follow the existing code style (tabs, explicit types, explicit accessibility modifiers)
- Add unit tests for new functionality
- Ensure
pnpm run lintandpnpm run test:cipass before opening a PR
MIT