listine
v21.1.6
Published
Angular virtual scroll component supporting variable item heights.
Downloads
110
Maintainers
Readme
Listine — Angular Variable-Height Virtual Scroller
A lightweight, high-performance virtual scroll component for Angular that renders only the items currently visible — and unlike most virtual scrollers, it works with variable (unknown) item heights out of the box.
Render lists with millions of rows smoothly, without freezing the browser.
Why Listine?
Most virtual scrollers (Angular CDK, PrimeNG, etc.) require a fixed item size. Listine measures each row as it renders, so it handles dynamic, content-driven heights without you predefining anything.
- True dynamic height support — measures real DOM heights, no fixed
itemSizerequired. - Built for huge lists — only visible rows live in the DOM, even with 1,000,000+ items.
- Signals-based & zoneless-friendly — modern Angular APIs (
input(),output(),signal(),effect()),OnPushchange detection. - Standalone component — no NgModule, drop it straight into any Angular app.
- Flexible templating — render each item with your own template, with access to the item and its index.
- Lazy-load ready —
scrollToEndoutput makes infinite scroll trivial.
Installation
npm install listineRequires Angular 21+ (
@angular/coreand@angular/common^21.0.0).
Quick Start
Import the standalone component and use it in your template.
import { Component } from "@angular/core";
import { VariableVirtualScrollComponent } from "listine";
@Component({
selector: "app-root",
standalone: true,
imports: [VariableVirtualScrollComponent],
template: `
<listine-variable-virtual-scroll
[items]="options"
[buffer]="10"
[viewportHeight]="600"
[itemTemplate]="itemTemplate"
(scrollToEnd)="loadMore()"
>
<ng-template #itemTemplate let-option let-i="index">
<p>{{ option.label }} (ID: {{ option.value }})</p>
</ng-template>
</listine-variable-virtual-scroll>
`,
})
export class AppComponent {
options = Array.from({ length: 1_000_000 }, (_, i) => ({
label: `Item ${i}`,
value: i.toString(),
}));
loadMore() {
// fetch / append the next page of data
}
}API
Inputs
| Input | Type | Default | Description |
| ------------------- | ------------------- | ------- | --------------------------------------------------------------------------- |
| items | any[] | [] | The full list of data to virtualize. |
| itemTemplate | TemplateRef<any> | — | Required. Template used to render each item. |
| viewportHeight | number | 400 | Height of the scrollable viewport, in pixels. |
| buffer | number | 5 | Extra rows rendered above/below the viewport for smoother scrolling. |
| initialItemHeight | number | 50 | Estimated row height (px) used before a row is measured. |
| panelOpen | boolean | false | Recalculates layout when shown inside a panel/overlay (prevents blank view).|
| scrollToTopTrigger| number | 0 | Change this value to reset the list to the top (e.g. after filter/search). |
Outputs
| Output | Payload | Description |
| --------------- | -------- | ------------------------------------------------------- |
| scrollEmitter | number | Emits the current scroll position on every scroll. |
| scrollToEnd | void | Emits when the user reaches the end — ideal for lazy load. |
Template context
The item template receives:
$implicit— the current item (e.g.let-option)index— the absolute index initems(e.g.let-i="index")
Public methods
| Method | Description |
| --------------- | ----------------------------------------------------- |
| scrollToTop() | Scrolls the list back to the top and recalculates rows. |
Tips
- Put the component in a container with a defined height; the scroll area is driven by
viewportHeight. - For filter/search, bump
scrollToTopTriggerso the user starts from the top of the new results.
Changelog
See CHANGELOG.md for the full release history.
