Mastering Angular CDK Virtual Scroll: Efficient Rendering of Massive Lists
Angular CDK Virtual Scroll renders only visible items, cutting DOM nodes and boosting performance for huge lists.
03 Aug 2026, 02:46 UTC

Problem: Rendering Thousands of Items Is Expensive
\nIn a typical Angular component you might iterate over a data array with *ngFor and display each row in a table or list. If the array contains 10,000 or more items, Angular creates 10,000 div nodes, each tracked by change detection. Even a simple table renders dozens of thousands of DOM nodes, leading to:
- \n
- Long initial paint times. \n
- Frequent garbage collection pauses. \n
- Heavy memory usage. \n
- Unresponsive scrolling. \n
Most users never scroll to the bottom of such a list, so rendering everything upfront is wasteful. The Angular CDK Virtual Scroll solves this by only rendering the items that are actually visible in the viewport.
\n\nHow Virtual Scroll Works
\nThe <cdk-virtual-scroll-viewport> component creates a container with a fixed height (or the height of its parent). Inside the viewport, you place an *cdkVirtualFor directive that behaves like *ngFor but only instantiates the subset of items that fit in the viewport plus a small buffer. The component tracks the scroll position and updates the rendered slice as the user scrolls.
Key points:
\n- \n
- All items must have the same height or provide an estimated size. For dynamic heights, the component can be told the average size, but large mis‑estimates can cause visual glitches. \n
- The viewport exposes
scrollToIndex(index: number)for programmatic navigation. \n - Because only a few DOM nodes exist at any time, the DOM tree remains shallow, and Angular’s change detection runs quickly. \n
Step‑by‑Step Implementation
\n- \n
- Create a new Angular project\n
\nng new virtual-scroll-demo --routing\ncd virtual-scroll-demo\n\n - Add the CDK package\n
\nng add @angular/cdk\n\n - Generate a component that will host the list\n
\nng generate component big-list\n\n - Define a large data array – in
big-list.component.ts:\n
\nexport class BigListComponent {\n items = Array.from({ length: 10000 }, (_, i) => `Item #${i + 1}`);\n}\n\n - Import the ScrollingModule in
app.module.ts:\n
\nimport { ScrollingModule } from '@angular/cdk/scrolling';\n@NgModule({\n imports: [ScrollingModule, ...]\n})\n\n - Build the template – in
big-list.component.html:\n
\n<cdk-virtual-scroll-viewport itemSize=\"50\" class="viewport">\n <div *cdkVirtualFor="let item of items; trackBy: trackById" class="item">\n {{ item }}\n </div>\n</cdk-virtual-scroll-viewport>\nExplanation:
\n- \n
itemSize=\"50\"tells the viewport that each row is 50 px tall. For variable heights, you can passestimatedSizeinstead. \n- Use a
trackByfunction to avoid recreating DOM nodes when the array changes. \n - Apply CSS to give the viewport a height, e.g.,
.viewport { height: 400px; overflow: auto; }. \n
\n - Optional: Expose a button to jump to a specific index\n
\n<button (click)="viewport.scrollToIndex(5000)">Jump to 5000</button>\n<cdk-virtual-scroll-viewport #viewport itemSize="50">\n ...\n</cdk-virtual-scroll-viewport>\n\n
Trade‑offs and Common Pitfalls
\n| Aspect | Benefit | Risk / Limitation |
|---|---|---|
| DOM size | Reduced to ~viewport height / itemSize nodes. | None if itemSize is accurate. |
| Memory usage | Lower because few nodes live. | Large arrays still occupy memory in the data model. |
| Change detection | Fast due to small view. | Missing trackBy causes full re‑creation on every change detection. |
| Dynamic heights | Supported via estimatedSize. | Large mis‑estimates can cause scroll jumps or blank spaces. |
| Nested scroll containers | None. | Only one scrollable viewport per component; nested scrolls can break event propagation. |
| SSR support | Not automatic. | Server‑side rendering requires additional setup to hydrate the virtual list. |
How to Verify the Performance Gain
\n- \n
- Run the app:
ng serveand openhttp://localhost:4200. \n - Open Chrome DevTools → Elements. Expand the
<cdk-virtual-scroll-viewport>and count its child<div>nodes. You should see only ~viewport height / itemSizenodes, not 10,000. \n - Switch to the Performance tab, record a short session while scrolling, and observe paint times. They should be noticeably lower than a plain *ngFor list. \n
- In the console, run
viewport.scrollToIndex(5000)(replaceviewportwith the actual ViewChild reference). The viewport should jump smoothly to item #5001. \n - Optional: Use
ng testwith a headless browser and a performance test harness to compare metrics programmatically. \n
Actionable Takeaway
\nIf your Angular app displays long tables or lists, start by wrapping the list in <cdk-virtual-scroll-viewport> and setting itemSize or estimatedSize. Add a trackBy function to keep change detection lean. Verify that the DOM node count stays low and that scrolling feels snappy. When you need to support variable heights, experiment with estimatedSize and adjust as needed. Remember that Virtual Scroll is a single viewport; avoid nesting scrollable containers. With these steps, you’ll keep your UI responsive even with tens of thousands of items.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.