The listed packages provide solutions for efficiently rendering large datasets in web applications by either loading content incrementally as the user scrolls (infinite scroll) or by only rendering visible items to reduce memory and performance overhead (virtualization). These libraries are framework-specific: ngx-infinite-scroll targets Angular, react-infinite-scroll-component, react-infinite-scroller, react-virtualized, and react-window serve React, while vue-infinite-loading and vue-virtual-scroller are built for Vue. Infinite scroll libraries typically trigger data fetching when the user nears the bottom of a container, whereas virtualized list libraries optimize rendering by recycling DOM elements for off-screen items.
When dealing with large datasets in web apps, two common strategies emerge: infinite scroll (load more data as the user scrolls) and virtualization (render only what’s visible). The right choice depends on your framework, data size, and performance needs. Let’s compare the leading libraries across Angular, React, and Vue.
These trigger a callback (e.g., fetch next page) when the user scrolls near the bottom. They do not limit DOM size—every loaded item stays in the DOM. Suitable for moderate data volumes or when full DOM access is needed (e.g., for search).
ngx-infinite-scrollreact-infinite-scroll-component (active), react-infinite-scroller (deprecated)vue-infinite-loadingThese render only visible items, recycling DOM nodes as the user scrolls. Essential for very large datasets (10k+ items) to avoid memory and performance issues.
react-window, react-virtualizedvue-virtual-scroller⚠️ Critical Note:
react-infinite-scrolleris deprecated. Its npm page states: "This package has been deprecated. Please usereact-infinite-scroll-componentinstead." Do not use it in new projects.
ngx-infinite-scrollAttach as a directive to a scrollable container. Triggers (scrolled) when nearing the bottom.
<!-- Angular template -->
<div class="container"
infiniteScroll
[infiniteScrollDistance]="2"
[infiniteScrollThrottle]="50"
(scrolled)="onScroll()">
<div *ngFor="let item of items">{{ item }}</div>
</div>
// Component
onScroll() {
this.loadMoreData();
}
No virtualization—each new item adds to the DOM. Best for feeds with <1000 items.
react-infinite-scroll-component (Recommended)Wraps your list and shows a loader while fetching.
import InfiniteScroll from 'react-infinite-scroll-component';
function MyList() {
return (
<InfiniteScroll
dataLength={items.length}
next={fetchMoreData}
hasMore={hasMore}
loader={<h4>Loading...</h4>}
>
{items.map(item => <div key={item.id}>{item.name}</div>)}
</InfiniteScroll>
);
}
Simple and effective—but again, all items stay in the DOM.
react-infinite-scroller (Deprecated)Avoid. Example shown only for legacy reference:
// DO NOT USE IN NEW PROJECTS
import InfiniteScroller from 'react-infinite-scroller';
<InfiniteScroller loadMore={loadFunc} hasMore={more}>
{items}
</InfiniteScroller>
react-window (Lightweight & Fast)Ideal for basic lists/grids. Uses FixedSizeList or VariableSizeList.
import { FixedSizeList as List } from 'react-window';
const Row = ({ index, style }) => (
<div style={style}>Row {index}</div>
);
<List
height={600}
itemCount={1000}
itemSize={35}
width="100%"
>
{Row}
</List>
Minimal API, excellent performance, small bundle.
react-virtualized (Feature-Rich)Supports advanced layouts like Grid, Table, and Masonry.
import { List } from 'react-virtualized';
<List
width={800}
height={600}
rowCount={list.length}
rowHeight={50}
rowRenderer={({ index, key, style }) => (
<div key={key} style={style}>{list[index]}</div>
)}
/>
Use only if you need dynamic measurements or complex layouts not covered by react-window.
vue-infinite-loadingComponent-based with clear loading states.
<template>
<div>
<div v-for="item in items" :key="item.id">{{ item.name }}</div>
<infinite-loading @infinite="infiniteHandler" />
</div>
</template>
<script>
export default {
methods: {
infiniteHandler($state) {
fetchData().then(data => {
if (data.length) {
this.items.push(...data);
$state.loaded();
} else {
$state.complete();
}
});
}
}
};
</script>
Clean integration with Vue’s reactivity. No virtualization.
vue-virtual-scrollerRenders only visible items with smooth scrolling.
<template>
<RecycleScroller
class="scroller"
:items="items"
:item-size="40"
key-field="id"
v-slot="{ item }"
>
<div>{{ item.name }}</div>
</RecycleScroller>
</template>
<script>
import { RecycleScroller } from 'vue-virtual-scroller';
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';
export default {
components: { RecycleScroller },
data() {
return { items: [...] };
}
};
</script>
Essential for long lists in Vue where performance matters.
| Concern | Infinite Scroll Libraries | Virtualized Libraries |
|---|---|---|
| DOM Size | Grows indefinitely | Constant (only visible items) |
| Memory Usage | Increases with loaded data | Stable |
| Search/Filter | Easy (all items in DOM) | Requires custom logic (items not in DOM) |
| Complex Layouts | Full CSS control | Limited to list/grid; harder for masonry |
| Best For | Feeds, timelines, moderate datasets (<1k) | Chat logs, analytics, huge datasets (10k+) |
react-infinite-scroller: Replace with react-infinite-scroll-component for same behavior, or switch to react-window if performance becomes an issue.ngx-infinite-scroll for simple cases; consider custom virtualization or third-party libs like @angular/cdk for large lists.react-infinite-scroll-component for simplicity. Switch to react-window when performance degrades. Use react-virtualized only for advanced layouts.vue-infinite-loading for paginated feeds. Use vue-virtual-scroller when you have thousands of items or notice slowdowns.Remember: Infinite scroll ≠ virtualization. Choose based on whether your bottleneck is network/data loading (infinite scroll) or DOM/rendering performance (virtualization).
Choose ngx-infinite-scroll if you're working in an Angular application and need a straightforward directive-based solution for triggering load-more callbacks during scrolling. It integrates cleanly with Angular's change detection and supports both window and container-based scrolling. Avoid it if you need advanced layout support like grids or variable-height items without additional custom logic.
Choose react-infinite-scroll-component for simple infinite scroll behavior in React apps where you want a minimal, component-based API that wraps your list and calls a function when more data is needed. It handles scroll detection and loader display out of the box but does not perform virtualization, so it’s best suited for moderate-sized lists where performance isn’t critically impacted by DOM size.
Avoid react-infinite-scroller in new projects—it is deprecated and no longer maintained. The package page on npm explicitly states it has been superseded by other solutions. If encountered in legacy code, plan a migration to react-infinite-scroll-component or a virtualized alternative depending on use case.
Choose react-virtualized when you need robust virtualization for complex layouts in React, including lists, grids, tables, and masonry. It offers extensive customization for item sizing, scrolling, and performance tuning but comes with a steeper learning curve and larger API surface. Prefer it over react-window only if you require features like dynamic row heights with measurement caching or advanced grid layouts.
Choose react-window for high-performance virtualized lists or grids in React when you prioritize simplicity and speed. It’s a lighter, faster successor to react-virtualized with a smaller API focused on fixed or variable-size lists and grids. Use it when you don’t need the extra features of react-virtualized and want minimal bundle impact with excellent runtime performance.
Choose vue-infinite-loading if you’re building a Vue 2 or Vue 3 app and need a clean, directive-like component for infinite scroll that shows loading states and triggers data fetches. It works well with server-paginated APIs and integrates smoothly with Vue’s reactivity system. It does not virtualize content, so pair it with manual DOM cleanup or use only when total rendered items remain manageable.
Choose vue-virtual-scroller when you need true virtual scrolling in Vue applications—rendering only visible items to maintain performance with thousands of records. It supports dynamic heights, recycling, and smooth scrolling out of the box. Ideal for chat logs, feeds, or any long list where DOM bloat would otherwise degrade performance.
versions now follow Angular's version to easily reflect compatibility.
Meaning, for Angular 10, use ngx-infinite-scroll @ ^10.0.0
Starting Angular 6 and Above - ngx-infinite-scroll@THE_VERSION.0.0
For Angular 4 and Angular = ^5.5.6 - use version ngx-infinite-scroll@0.8.4
For Angular 5.x with rxjs =<5.5.2 - use version ngx-infinite-scroll@0.8.3
For Angular version <= 2.3.1, you can use npm i angular2-infinite-scroll (latest version is 0.3.42) - please notice the angular2-infinite-scroll package is deprecated
and much more.
These analytics are made available via the awesome Scarf package analytics library
Scarf can be disabled by following these directions
I'm a Senior Front End Engineer & Consultant at Orizens. My services include:
npm install ngx-infinite-scroll --save
| @Input() | Type | Required | Default | Description |
|---|---|---|---|---|
| infiniteScrollDistance | number | optional | 2 | the bottom percentage point of the scroll nob relatively to the infinite-scroll container (i.e, 2 (2 * 10 = 20%) is event is triggered when 80% (100% - 20%) has been scrolled). if container.height is 900px, when the container is scrolled to or past the 720px, it will fire the scrolled event. |
| infiniteScrollUpDistance | number | optional | 1.5 | should get a number |
| infiniteScrollThrottle | number | optional | 150 | should get a number of milliseconds for throttle. The event will be triggered this many milliseconds after the user stops scrolling. |
| scrollWindow | boolean | optional | true | listens to the window scroll instead of the actual element scroll. this allows to invoke a callback function in the scope of the element while listenning to the window scroll. |
| immediateCheck | boolean | optional | false | invokes the handler immediately to check if a scroll event has been already triggred when the page has been loaded (i.e. - when you refresh a page that has been scrolled) |
| infiniteScrollDisabled | boolean | optional | false | doesn't invoke the handler if set to true |
| horizontal | boolean | optional | false | sets the scroll to listen for horizontal events |
| alwaysCallback | boolean | optional | false | instructs the scroller to always trigger events |
| infiniteScrollContainer | string / HTMLElement | optional | null | should get a html element or css selector for a scrollable element; window or current element will be used if this attribute is empty. |
| fromRoot | boolean | optional | false | if infiniteScrollContainer is set, this instructs the scroller to query the container selector from the root of the document object. |
| @Output() | Type | Event Type | Required | Description |
|---|---|---|---|---|
| scrolled | EventEmitter | IInfiniteScrollEvent | optional | this will callback if the distance threshold has been reached on a scroll down. |
| scrolledUp | EventEmitter | IInfiniteScrollEvent | optional | this will callback if the distance threshold has been reached on a scroll up. |
By default, the directive listens to the window scroll event and invoked the callback.
To trigger the callback when the actual element is scrolled, these settings should be configured:
In this example, the onScroll callback will be invoked when the window is scrolled down:
import { Component } from '@angular/core';
import { InfiniteScrollDirective } from 'ngx-infinite-scroll';
@Component({
selector: 'app',
template: `
<div
class="search-results"
infiniteScroll
[infiniteScrollDistance]="2"
[infiniteScrollThrottle]="50"
(scrolled)="onScroll()"
></div>
`,
imports: [InfiniteScrollDirective]
})
export class AppComponent {
onScroll() {
console.log('scrolled!!');
}
}
in this example, whenever the "search-results" is scrolled, the callback will be invoked:
import { Component } from '@angular/core';
import { InfiniteScrollDirective } from 'ngx-infinite-scroll';
@Component({
selector: 'app',
styles: [
`
.search-results {
height: 20rem;
overflow: scroll;
}
`,
],
template: `
<div
class="search-results"
infiniteScroll
[infiniteScrollDistance]="2"
[infiniteScrollThrottle]="50"
(scrolled)="onScroll()"
[scrollWindow]="false"
></div>
`,
imports: [InfiniteScrollDirective]
})
export class AppComponent {
onScroll() {
console.log('scrolled!!');
}
}
In this example, the onScrollDown callback will be invoked when the window is scrolled down and the onScrollUp callback will be invoked when the window is scrolled up:
import { Component } from '@angular/core';
import { InfiniteScroll } from 'ngx-infinite-scroll';
@Component({
selector: 'app',
directives: [InfiniteScroll],
template: `
<div
class="search-results"
infiniteScroll
[infiniteScrollDistance]="2"
[infiniteScrollUpDistance]="1.5"
[infiniteScrollThrottle]="50"
(scrolled)="onScrollDown()"
(scrolledUp)="onScrollUp()"
></div>
`,
})
export class AppComponent {
onScrollDown() {
console.log('scrolled down!!');
}
onScrollUp() {
console.log('scrolled up!!');
}
}
In this example, the infiniteScrollContainer attribute is used to point directive to the scrollable container using a css selector. fromRoot is used to determine whether the scroll container has to be searched within the whole document ([fromRoot]="true") or just inside the infiniteScroll directive ([fromRoot]="false", default option).
import { Component } from '@angular/core';
import { InfiniteScrollDirective } from 'ngx-infinite-scroll';
@Component({
selector: 'app',
styles: [
`
.main-panel {
height: 100px;
overflow-y: scroll;
}
`,
],
template: `
<div class="main-panel">
<div
infiniteScroll
[infiniteScrollDistance]="2"
[infiniteScrollThrottle]="50"
[infiniteScrollContainer]="selector"
[fromRoot]="true"
(scrolled)="onScroll()"
></div>
</div>
`,
imports: [InfiniteScrollDirective]
})
export class AppComponent {
selector: string = '.main-panel';
onScroll() {
console.log('scrolled!!');
}
}
It is also possible to use infiniteScrollContainer without additional variable by using single quotes inside double quotes:
[infiniteScrollContainer]="'.main-panel'"
This project exists thanks to all the people who contribute. [Contribute].
Thank you to all our backers! 🙏 [Become a backer]