Home/Performance/Row Virtualisation
Performance

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.

#
Reference
Customer
Region
Amount
Status
1
ORD-000001
Account 2242
East
381.53
Pending
2
ORD-000002
Account 2066
Central
356.76
Refunded
3
ORD-000003
Account 3950
West
376.48
Pending
4
ORD-000004
Account 0720
Central
437.75
Refunded
5
ORD-000005
Account 2568
Central
214.18
Refunded
6
ORD-000006
Account 0664
West
311.31
Refunded
7
ORD-000007
Account 1158
Central
243.09
Pending
8
ORD-000008
Account 3130
West
454.13
Pending
9
ORD-000009
Account 1127
North
78.74
Pending
10
ORD-000010
Account 0692
North
315.94
Pending
11
ORD-000011
Account 2114
North
288.27
Paid
12
ORD-000012
Account 0638
West
93.89
Pending
13
ORD-000013
Account 1715
North
404.83
Pending
14
ORD-000014
Account 1107
West
127.03
Refunded
15
ORD-000015
Account 0523
West
30.61
Pending
16
ORD-000016
Account 3819
East
284.52
Paid
17
ORD-000017
Account 1296
West
41.47
Pending
18
ORD-000018
Account 3745
South
178.62
Refunded
19
ORD-000019
Account 1566
North
202.83
Refunded
20
ORD-000020
Account 2091
West
350.03
Refunded
21
ORD-000021
Account 2615
North
7.23
Pending
22
ORD-000022
Account 0320
South
313.25
Pending
23
ORD-000023
Account 1964
Central
153.5
Paid
24
ORD-000024
Account 1800
East
129.1
Refunded
25
ORD-000025
Account 0694
East
239.36
Pending
26
ORD-000026
Account 1403
East
175.74
Pending
80 / 10,000 records

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

Reference
Region
Note
ORD-000001
East
Paid on time.
ORD-000002
Central
Customer asked for the invoice to be reissued against the parent account, and the finance team confirmed the change on the same day.
ORD-000003
West
Partial refund pending.
ORD-000004
Central
Delivery was split across two shipments because the second warehouse was out of stock; the customer accepted the later date rather than cancelling, and both parcels were signed for.
ORD-000005
Central
No notes.
ORD-000006
West
Paid on time.
ORD-000007
Central
Customer asked for the invoice to be reissued against the parent account, and the finance team confirmed the change on the same day.
ORD-000008
West
Partial refund pending.
ORD-000009
North
Delivery was split across two shipments because the second warehouse was out of stock; the customer accepted the later date rather than cancelling, and both parcels were signed for.
ORD-000010
North
No notes.
ORD-000011
North
Paid on time.
ORD-000012
West
Customer asked for the invoice to be reissued against the parent account, and the finance team confirmed the change on the same day.
ORD-000013
North
Partial refund pending.
ORD-000014
West
Delivery was split across two shipments because the second warehouse was out of stock; the customer accepted the later date rather than cancelling, and both parcels were signed for.
ORD-000015
West
No notes.
ORD-000016
East
Paid on time.
ORD-000017
West
Customer asked for the invoice to be reissued against the parent account, and the finance team confirmed the change on the same day.
ORD-000018
South
Partial refund pending.
ORD-000019
North
Delivery was split across two shipments because the second warehouse was out of stock; the customer accepted the later date rather than cancelling, and both parcels were signed for.
ORD-000020
West
No notes.
ORD-000021
North
Paid on time.
ORD-000022
South
Customer asked for the invoice to be reissued against the parent account, and the finance team confirmed the change on the same day.
ORD-000023
Central
Partial refund pending.
ORD-000024
East
Delivery was split across two shipments because the second warehouse was out of stock; the customer accepted the later date rather than cancelling, and both parcels were signed for.
ORD-000025
East
No notes.
ORD-000026
East
Paid on time.
60 / 400 records

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

Reference
Region
Note
ORD-000001
East
Paid on time.
ORD-000002
Central
Customer asked for the invoice to be reissued against the parent account, and the finance team confirmed the change on the same day.
ORD-000003
West
Partial refund pending.
ORD-000004
Central
Delivery was split across two shipments because the second warehouse was out of stock; the customer accepted the later date rather than cancelling, and both parcels were signed for.
ORD-000005
Central
No notes.
ORD-000006
West
Paid on time.
ORD-000007
Central
Customer asked for the invoice to be reissued against the parent account, and the finance team confirmed the change on the same day.
ORD-000008
West
Partial refund pending.
ORD-000009
North
Delivery was split across two shipments because the second warehouse was out of stock; the customer accepted the later date rather than cancelling, and both parcels were signed for.
ORD-000010
North
No notes.
ORD-000011
North
Paid on time.
ORD-000012
West
Customer asked for the invoice to be reissued against the parent account, and the finance team confirmed the change on the same day.
ORD-000013
North
Partial refund pending.
ORD-000014
West
Delivery was split across two shipments because the second warehouse was out of stock; the customer accepted the later date rather than cancelling, and both parcels were signed for.
ORD-000015
West
No notes.
ORD-000016
East
Paid on time.
ORD-000017
West
Customer asked for the invoice to be reissued against the parent account, and the finance team confirmed the change on the same day.
ORD-000018
South
Partial refund pending.
ORD-000019
North
Delivery was split across two shipments because the second warehouse was out of stock; the customer accepted the later date rather than cancelling, and both parcels were signed for.
ORD-000020
West
No notes.
ORD-000021
North
Paid on time.
ORD-000022
South
Customer asked for the invoice to be reissued against the parent account, and the finance team confirmed the change on the same day.
ORD-000023
Central
Partial refund pending.
ORD-000024
East
Delivery was split across two shipments because the second warehouse was out of stock; the customer accepted the later date rather than cancelling, and both parcels were signed for.
ORD-000025
East
No notes.
ORD-000026
East
Paid on time.
60 / 400 records

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.

Reference
Region
Note
ORD-000001
East
Paid on time.
ORD-000002
Central
Customer asked for the invoice to be reissued against the parent account, and the finance team confirmed the change on the same day.
ORD-000003
West
Partial refund pending.
ORD-000004
Central
Delivery was split across two shipments because the second warehouse was out of stock; the customer accepted the later date rather than cancelling, and both parcels were signed for.
ORD-000005
Central
No notes.
ORD-000006
West
Paid on time.
ORD-000007
Central
Customer asked for the invoice to be reissued against the parent account, and the finance team confirmed the change on the same day.
ORD-000008
West
Partial refund pending.
ORD-000009
North
Delivery was split across two shipments because the second warehouse was out of stock; the customer accepted the later date rather than cancelling, and both parcels were signed for.
ORD-000010
North
No notes.
ORD-000011
North
Paid on time.
ORD-000012
West
Customer asked for the invoice to be reissued against the parent account, and the finance team confirmed the change on the same day.
ORD-000013
North
Partial refund pending.
ORD-000014
West
Delivery was split across two shipments because the second warehouse was out of stock; the customer accepted the later date rather than cancelling, and both parcels were signed for.
ORD-000015
West
No notes.
ORD-000016
East
Paid on time.
ORD-000017
West
Customer asked for the invoice to be reissued against the parent account, and the finance team confirmed the change on the same day.
ORD-000018
South
Partial refund pending.
ORD-000019
North
Delivery was split across two shipments because the second warehouse was out of stock; the customer accepted the later date rather than cancelling, and both parcels were signed for.
ORD-000020
West
No notes.
ORD-000021
North
Paid on time.
ORD-000022
South
Customer asked for the invoice to be reissued against the parent account, and the finance team confirmed the change on the same day.
ORD-000023
Central
Partial refund pending.
ORD-000024
East
Delivery was split across two shipments because the second warehouse was out of stock; the customer accepted the later date rather than cancelling, and both parcels were signed for.
ORD-000025
East
No notes.
ORD-000026
East
Paid on time.
60 / 400 records

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 valueGetter or 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

PropertyTypeDefaultDescription
config.rowHeightnumber—Height of a row in px. With uniform rows this is the whole position calculation; with autoRowHeight it is the starting estimate.
config.heightnumber | string—The grid needs a height before it has a viewport to window. 'auto' renders every loaded row instead — see Sizing & Height.
config.autoRowHeightbooleanfalseLet each row grow to fit its tallest cell. Rows are then positioned from measured heights rather than from rowHeight.
config.trackBystring | ((row: T) => unknown)—Row identity. Lets the grid reuse a row element instead of rebuilding it — see Row Identity.