Build an Infinite Scroll in Ionic: A Beginner-Friendly Guide

Alexey Karimov

If you’re building a mobile app that loads a long list of data, you don’t want to fetch everything up front. Loading too much data in one go affects performance and makes your app feel slow. But loading only a fixed number of items per page can also feel limiting and clunky.

If you want the list to grow as the user scrolls without them clicking a “next” button, then you might want to use <ion-infinite-scroll>.

In this guide, we’ll build a simple working example of infinite scroll using Ionic and Angular. The goal is to help you understand how it works and get something up and running quickly.

Project Setup: What You’ll Build

A simple Ionic app that loads random user data as the user scrolls down. No extra libraries, just HttpClient and a public API.

To get started, create a new Ionic Angular app using the CLI: ionic start infinite-scroll-demo blank --type=angular and then move into your new project folder: cd infinite-scroll-demo. Once done, generate a service to fetch data using ionic generate service data.

We’ll use this service to return a list of items page by page, mimicking a paginated API. Let’s proceed with the next steps.

Step 1: Add HttpClientModule

Ionic uses Angular’s HttpClient to make API calls. But this feature isn’t available by default. You need to import HttpClientModule into your main AppModule first. This step unlocks the ability to fetch external data, which is essential when your scroll event needs to load more users dynamically from an API.

import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [...],
  imports: [
    BrowserModule,
    IonicModule.forRoot(),
    AppRoutingModule,
    HttpClientModule
  ],
  ...
})
export class AppModule {}

Step 2: Create a UserService to Fetch Data

Fetching data directly from a component clutters logic and makes it harder to reuse. By isolating HTTP logic inside a UserService, you create a cleaner separation of concerns. Inside your previously created data.service.ts, add this:

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { map } from 'rxjs/operators';

@Injectable({
  providedIn: 'root',
})
export class DataService {
  constructor(private http: HttpClient) {}

  fetchUsers(page: number = 1, results: number = 10) {
    return this.http
      .get(`https://randomuser.me/api/?page=${page}&results=${results}`)
      .pipe(map((res: any) => res.results));
  }
}

The service uses the randomuser.me API to fetch 10 users per page. This paginated format makes it easier to load small, manageable chunks as the user scrolls, instead of flooding the app with all the data at once.

Step 3: Build the Infinite Scroll UI

This is where we visually present the data. Ionic’s <ion-list> and <ion-item> handle rendering the users, while <ion-infinite-scroll> watches how close the user is to the bottom of the list. Once it crosses the threshold, it triggers a scroll event. The combination of these components builds the illusion of a “never-ending” feed that loads progressively.

In src/app/home/home.page.html, add the following layout: 

<ion-header>
  <ion-toolbar>
    <ion-title>Infinite Scroll</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-list>
    <ion-item *ngFor="let user of users">
      <ion-avatar slot="start">
        <img [src]="user.picture.thumbnail" />
      </ion-avatar>
      <ion-label>
        <h2>{{ user.name.first }} {{ user.name.last }}</h2>
        <p>{{ user.email }}</p>
      </ion-label>
    </ion-item>
  </ion-list>

  <ion-infinite-scroll threshold="100px" (ionInfinite)="loadMore($event)">
    <ion-infinite-scroll-content
      loadingSpinner="bubbles"
      loadingText="Loading more users...">
    </ion-infinite-scroll-content>
  </ion-infinite-scroll>
</ion-content>

This sets up a scrollable list. Each item shows a user’s thumbnail, name, and email.

Step 4: Load More Data on Scroll

The next step is to connect the scroll trigger to actual logic. When the scroll event fires, loadMore() increments the page count and fetches the next set of users. Once the data is appended to the existing list, calling event.target.complete() signals Ionic to stop showing the spinner and reset the scroll state. 

Without this method, the scroll component would stay stuck in a loading state. In src/app/home/home.page.ts, implement logic to handle infinite loading:

import { Component, OnInit } from '@angular/core';
import { DataService } from '../data.service';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage implements OnInit {
  users = [];
  currentPage = 1;

  constructor(private dataService: DataService) {}

  ngOnInit() {
    this.loadUsers();
  }

  loadUsers() {
    this.dataService.fetchUsers(this.currentPage).subscribe((res) => {
      this.users.push(...res);
    });
  }

  loadMore(event: any) {
    this.currentPage++;
    this.dataService.fetchUsers(this.currentPage).subscribe((res) => {
      this.users.push(...res);
      event.target.complete();
    });
  }
}

We start with page 1, then fetch the next batch as the scroll triggers loadMore().

Step 5: Run and Test It

Even though your code might be fine, infinite scroll often feels broken when testing, especially if there’s not enough content to trigger the scroll threshold. 

That’s why testing on a mobile device or emulator with a smaller screen height gives more realistic behavior. You can also simulate threshold distance by adjusting the browser window or tweaking the threshold value.

Use the following command to start your app: ionic serve

You can also use ionic capacitor run android or ionic capacitor run ios if you want to test it on a device.

To simulate infinite scroll properly, reduce your browser window height or use a mobile emulator. The threshold (100px) defines how far from the bottom the event triggers. You can change it to ‘10%‘ or a different value if needed.

Wrapping Up

That’s it, you’ve just built a working infinite scroll in Ionic using real API data. As the user scrolls, the app fetches more items and updates the list with a smooth loading indicator.

As a best practice, always call complete() after loading data. If you skip it, the spinner won’t go away. You can also customize the experience using loadingSpinner and loadingText, and disable the scroll once there’s no more data to fetch.

Use trackBy with *ngFor to boost rendering performance. And remember, infinite scroll works best for long lists. For short datasets, it’s better to show everything at once.

Need reverse scroll? Use position=”top”. For accessibility, wrap the list with role=”feed” and items with role=”article” or use <article>.
Want to go deeper? Check out the official Ionic Infinite Scroll docs.