Row Virtualisation
Virtual scrolling keeps only the rows around the viewport in the DOM, so ten thousand rows cost about the same to render as a hundred. Every example below counts what is actually on the page while you scroll it.
The window, live
Ten thousand rows in a 460px grid. Scroll to the very bottom — the number of rows in the DOM does not grow.
0 of 10,000 rows are in the DOM right now.
Rows of one height
Every row is 40px, so the grid never measures one — a row's position is its index times rowHeight.
0 rows in the DOM · 0 distinct row heights
Rows of different heights
The same data with autoRowHeight on. Rows now vary, so the grid positions them from the heights it measured — and the window is still bounded.
0 rows in the DOM · 0 distinct row heights
The row you were reading stays put
Scroll down a little, then open a row ABOVE the one you are looking at. The row you were reading does not move.
Only the rows you can see exist
The grid renders a window of rows around the viewport and replaces it as you scroll. Ten thousand rows, a hundred thousand rows and a million rows all produce a DOM of the same size — what changes is the height of the scroll area, not the number of elements.
That is why the counters above barely move while you scroll, and why the grid can be given a dataset far larger than a browser could ever render at once.
The window is bigger than the viewport, on purpose
The grid keeps 15 extra rows above and below what you can see. Without that
overscan a fast scroll would show a blank band for a frame while the next rows were
prepared; with it, the rows are already in place when they arrive on screen.
So a grid showing about a dozen rows reports roughly forty in the DOM rather than a dozen. Both numbers are small, and neither grows with your data.
Two ways a row gets its position
When every row is the same height, a row's position is its index multiplied by
rowHeight. Nothing is measured and nothing is remembered — this is what a
grid does by default.
Set autoRowHeight, open a row, or span one across others, and rows stop
being interchangeable. The grid then works from the heights it has actually measured, so
the window stays exact — it costs a little bookkeeping instead of a multiplication. The
two examples above are the same data on each path.
The row you were reading stays where it was
When something above your scroll position changes height — a row opens, or a measured row turns out taller than its estimate — everything below it would normally slide down. The grid compensates, so the row you were looking at stays where it was instead of jumping away from you.
What your app needs to do
The grid is built for OnPush change detection and runs without Zone.js.
There is nothing to opt into, but two things on your side decide whether you get the
behaviour described on this page:
-
Give rows a stable identity — set
trackBy. Without it the grid cannot tell an existing row from a new one, and a data change rebuilds rows that could have been reused. -
Keep cell templates cheap — a template runs for every row in the
window, every time the window moves. Work that belongs in a
valueGetteror in your data should not sit in a template.
If your application still runs with Zone.js the grid works unchanged — it does not require a zoneless application.
Virtualisation options
| Property | Type | Default | Description |
|---|---|---|---|
| config.rowHeight | number | — | Height of a row in px. With uniform rows this is the whole position calculation; with autoRowHeight it is the starting estimate. |
| config.height | number | string | — | The grid needs a height before it has a viewport to window. 'auto' renders every loaded row instead — see Sizing & Height. |
| config.autoRowHeight | boolean | false | Let each row grow to fit its tallest cell. Rows are then positioned from measured heights rather than from rowHeight. |
| config.trackBy | string | ((row: T) => unknown) | — | Row identity. Lets the grid reuse a row element instead of rebuilding it — see Row Identity. |