PrimeNG VirtualScroller: Rendering Thousands of Rows Without Slowing Down Your Angular App
If your Angular app needs to show large lists, a plain *ngFor can choke the UI. PrimeNG’s VirtualScroller keeps the DOM tiny by only rendering visible rows. Learn how to set it up, what limits to watch, and how to verify it works.
18 Aug 2025, 18:29 UTC

The Problem: Rendering Thousands of Rows
Angular’s *ngFor renders a DOM node for every element in the bound array. When you bind to a list of 10,000 items, the browser creates 10,000 div (or tr) elements. Even with lazy loading of the data, the UI becomes sluggish, memory spikes, and scrolling feels choppy.
How VirtualScroller Works
PrimeNG’s <p-virtualscroller> solves this by keeping a small, fixed‑size pool of DOM nodes. Only the items that fall inside the viewport are rendered. As the user scrolls, the component recycles the existing nodes and updates their content, giving the illusion of a full list while actually maintaining a handful of elements.
The core idea is simple:
- Container – A scrollable element with a defined height and
overflow:auto. - Item Size – A constant pixel height for each row (
itemSize). - Visible Window – The number of items that fit in the container’s height.
- Recycling – When scrolling, the component updates the content of the recycled nodes instead of creating new ones.
Setting It Up in Your App
Below is a minimal, fully functional example. It assumes you have PrimeNG 11+ and Angular 9+ installed.
<p-virtualscroller
[value]="items"
[itemSize]="44"
[style]="{height: '400px'}"
>
<ng-template let-item>
<div class="item-row">
{{item.id}} – {{item.name}}
</div>
</ng-template>
</p-virtualscroller>
In your component class:
// app.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent implements OnInit {
items: Array<{id: number, name: string}> = [];
ngOnInit() {
// Simulate a large data set
for (let i = 1; i <= 10000; i++) {
this.items.push({ id: i, name: `Item ${i}` });
}
}
}
Key points:
itemSizemust match the CSS height of.item-row. If you style the row withheight: 44px;, the VirtualScroller can calculate how many items fit in the viewport.- The container’s
height(here 400px) andoverflow:auto(default for<p-virtualscroller>) create the scrollable area. - All heavy lifting is handled by PrimeNG; you only provide the data array and a template.
What to Watch Out For
VirtualScroller is powerful but has a couple of caveats:
- Fixed Item Height: The component assumes every row is the same height. Variable‑height rows can cause mis‑alignment or flicker. If your content is dynamic, either enforce a uniform height via CSS (e.g.,
min-heightandline-height) or upgrade to PrimeNG 12+, which adds aflexHeightmode that can adapt to variable heights at the cost of extra calculations. - Container CSS: If you forget
overflow:autoor set the height toauto, the scroll events won’t be captured and the component will try to render all items, defeating the purpose. - Complex Templates: Heavy logic inside the
ng-templatecan still impact performance. Keep the template lean; if you need complex rendering, consider lazy loading the data as the user scrolls.
Putting It to the Test
After wiring up the component, verify its behavior:
- Open the application in a modern browser and open DevTools.
- Inspect the DOM inside
<p-virtualscroller>. You should see roughlyvisibleItems + 2divelements (the buffer). For a 400px container with 44px rows, that’s about 10 visible items plus a couple of buffer nodes. - Scroll down and observe that the number of
divelements stays constant; only their content changes. - Check the console for any warnings about
itemSizemismatches or overflow issues.
If you notice the number of rendered nodes growing as you scroll, double‑check the itemSize value and the CSS height of the row.
Bottom Line
PrimeNG’s VirtualScroller is a pragmatic solution for rendering large data sets in Angular. It keeps the DOM light, improves scroll performance, and integrates seamlessly with existing PrimeNG styling. The trade‑off is the requirement for a predictable item height, but with careful CSS or the newer flexHeight mode, you can mitigate that risk. Combine it with server‑side pagination or lazy loading for the best results when fetching data is also expensive.
Give it a try on your next project that deals with long lists, and watch the UI feel snappy again.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.