listine
Version:
Angular virtual scroll component supporting variable item heights.
131 lines (92 loc) • 5.08 kB
Markdown
# Listine — Angular Variable-Height Virtual Scroller
[](https://www.npmjs.com/package/listine)
[](https://www.npmjs.com/package/listine)
[](https://github.com/hrssh11/listine/blob/main/LICENSE)
[](https://angular.dev)
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 `itemSize` required.
- **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()`), `OnPush` change 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** — `scrollToEnd` output makes infinite scroll trivial.
---
## Installation
```bash
npm install listine
```
> Requires **Angular 21+** (`@angular/core` and `@angular/common` `^21.0.0`).
---
## Quick Start
Import the standalone component and use it in your template.
```ts
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 in `items` (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 `scrollToTopTrigger` so the user starts from the top of the new results.
---
## Changelog
See [CHANGELOG.md](https://github.com/hrssh11/listine/blob/main/CHANGELOG.md) for the full release history.
## Author
- [@hrssh11](https://www.github.com/hrssh11)
## License
[MIT](LICENSE)