Virtual Scroll
100,000 rows in one scrollable list — only the visible window is rendered into the DOM. Open DevTools and watch the element count while scrolling.
virtualScroll rowHeight overscan
100.000 rows
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeColumn, OgeGrid } from '@oge-ui/grid';
@Component({
selector: 'demo-root',
imports: [OgeColumn, OgeGrid],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- give the grid a bounded height and switch virtualScroll on -->
<oge-grid [data]="employees" keyField="id"
[virtualScroll]="true" [rowHeight]="36" [overscan]="6"
class="h-[560px]">
<oge-column field="id" caption="Id" [width]="90" dataType="number" />
<oge-column field="firstName" caption="First Name" />
<oge-column field="salary" caption="Salary" dataType="number" />
</oge-grid>
`,
})
export class Demo {
readonly employees = [
{ id: 1, firstName: 'Ali', lastName: 'Yılmaz', department: 'Engineering', city: 'İstanbul', salary: 8400, hireDate: '2019-03-11' },
{ id: 2, firstName: 'Ayşe', lastName: 'Kaya', department: 'Sales', city: 'Ankara', salary: 7200, hireDate: '2020-07-02' },
{ id: 3, firstName: 'Mehmet', lastName: 'Demir', department: 'Engineering', city: 'İzmir', salary: 9100, hireDate: '2018-01-23' },
{ id: 4, firstName: 'Zeynep', lastName: 'Şahin', department: 'Finance', city: 'İstanbul', salary: 6800, hireDate: '2021-11-15' },
{ id: 5, firstName: 'Emre', lastName: 'Çelik', department: 'Support', city: 'Bursa', salary: 5400, hireDate: '2022-05-09' },
];
}How it works
- Row offsets live in a Fenwick (binary-indexed) tree: finding the row at any scroll position and the total height are both O(log n) — scrolling cost does not grow with list size.
- Only the visible rows plus an
overscanbuffer (default 6) exist in the DOM; a spacer element keeps the scrollbar honest. - Group and master-detail rows participate with their own heights (
detailRowHeight). - Keyboard navigation scrolls the focused row into view automatically, even across 100k rows.
Column virtualization
Wide grids get the same treatment horizontally: columnRenderingMode: 'virtual' renders only the columns near the horizontal viewport and stands spacer tracks in for the rest. Scroll sideways below — the DOM holds a couple dozen of the 200 columns at any time.
200 columns 1.000 rows
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeGrid } from '@oge-ui/grid';
import type { OgeColumnDef } from '@oge-ui/grid';
@Component({
selector: 'demo-root',
imports: [OgeGrid],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- 200 columns: only the ones near the horizontal viewport are rendered -->
<oge-grid [data]="wideRows" keyField="c0"
[columns]="wideColumns"
[scrolling]="{ mode: 'virtual', columnRenderingMode: 'virtual' }"
class="h-[420px]">
</oge-grid>
`,
})
export class Demo {
protected readonly wideColumns: OgeColumnDef[] = Array.from(
{ length: 200 },
(_, i) => ({ field: `c${i}`, caption: `Column ${i}`, width: 120 }),
);
protected readonly wideRows = Array.from({ length: 500 }, (_, row) =>
Object.fromEntries(
this.wideColumns.map((column, i) => [column.field, row * 200 + i]),
),
);
}Variable row heights
With autoRowHeight the virtualizer stops assuming a fixed row height: rendered rows are measured, measurements feed the offset tree, and corrections above the viewport are compensated on scrollTop in the same frame — no visible jump while scrolling through wrapped content.
5.000 rows autoRowHeight wordWrap
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeColumn, OgeGrid } from '@oge-ui/grid';
@Component({
selector: 'demo-root',
imports: [OgeColumn, OgeGrid],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- rows size to their content; measured heights feed the virtualizer -->
<oge-grid [data]="notes" keyField="id"
[virtualScroll]="true" [autoRowHeight]="true" [wordWrap]="true"
class="h-[420px]">
<oge-column field="id" caption="Id" [width]="70" dataType="number" />
<oge-column field="title" caption="Title" [width]="180" />
<oge-column field="body" caption="Body" />
</oge-grid>
`,
})
export class Demo {
protected readonly notes = [
{ id: 1, title: 'Kickoff', body: 'Short note.' },
{ id: 2, title: 'Retro', body: 'A much longer note that wraps over several lines and makes the row taller than its neighbours.' },
];
}