Angular - Ag Grid server-side infinity scroll

Angular - Ag Grid server-side infinity scroll allows for efficient rendering of large datasets by fetching data as the user scrolls. Learn how to implement it.

Ag Grid

I had a task to show the large amount of data in the ag-grid table. It’s almost 7k+ data in my local environment(In the production environment, I hope It will be almost 50k+). It’s not possible to load all the data from the server at once and show it on the ag-grid table. Traditionally, when dealing with large datasets in web applications, developers have to load all the data at once, which can cause performance issues such as slow page loading times and high memory usage. Server-side infinite scrolling solves these problems by fetching data from the server in smaller batches, as the user scrolls down the page.

By using server-side infinite scrolling, you can significantly reduce the amount of data that needs to be loaded at once, improving the performance of your application. Additionally, it allows you to work with much larger datasets without experiencing any lag or delays.

How Server-Side Infinite Scrolling Works in Ag-Grid

Ag-Grid’s server-side infinite scrolling feature is implemented using a combination of server-side pagination and row models. When a user scrolls down the grid, Ag-Grid requests the next set of data from the server, which is then appended to the existing data in the grid.

Ag-Grid provides three different row models for server-side infinite scrolling: the Infinite Row Model, the Server-Side Row Model, and Viewport Row Model. The Server-side and Viewport are only available in the enterprise edition. As I’m using the Community version. That’s why I’m using the Infinite scroll Row Model to implement the infinite scroll feature.

The Infinite Row Model is used when you have a fixed set of data that is loaded from the server, but you want to display only a subset of that data in the grid at any given time. The grid will request additional data from the server as the user scrolls down the page, ensuring that only the necessary data is loaded.

Implementing server-side infinite scrolling in Ag-Grid involves a few key steps:

  1. Implement a server-side endpoint that can handle pagination requests from the grid.
  2. Configure Ag-Grid to use the appropriate row model for server-side infinite scrolling.
  3. Configure Ag-Grid to use the endpoint you created in step 1 to fetch data from the server.
  4. Handle any necessary data transformations on the server before returning the data to Ag-Grid.
  5. Configure Ag-Grid to display the data in the appropriate format.

In this article, I will show all the angular/components Github Issues in the ag-grid table. To load all the issues, I will use the GitHub issue rest API.

The API URL: https://api.github.com/search/issues?q=repo:angular/components&sort=created&order=desc&page=1&per_page=100

Explanation about query params:

?q=repo:angular/components is a query parameter that specifies the search query. In this case, we’re searching for issues in the “angular/components” repository on GitHub. The q parameter is used to specify the query string.

&sort=created is a query parameter that specifies how the search results should be sorted. In this case, we’re sorting by the creation date of the issues.

&order=desc is a query parameter that specifies the order in which the search results should be sorted. In this case, we’re sorting in descending order (i.e., most recent issues first).

&page=1 is a query parameter that specifies which page of results to return. In this case, we’re requesting the first page of the results. But when we scroll down at the bottom of the table, it will send another request and this time the page will add +1 with the current page number.

&per_page=100 is a query parameter that specifies how many results should be returned per page. In this case, we’re requesting 100 results per page.

Prerequisites

Before we get started, make sure you have the following installed on your machine:

  1. Node.js version 12 or higher
  2. NPM
  3. @angular/cli

At first, We need to create an angular project using the following command:

ng new ag-grid-server-side-project

And then navigate to the workspace folder:

cd ag-grid-server-side-project

Install the following ag-grid required packages :

"@ag-grid-community/all-modules": "^27.3.0"
"ag-grid-community": "^29.2.0"
"ag-grid-angular": "^29.2.0"

We need to do the following steps to implement the ag-grid server-side infinite scroll feature.

   export interface IGithubIssue {
     created_at: string;
     number: string;
     state: string;
     title: string;
     id: string;
}
   export interface IGithubApi {
     items: IGithubIssue[];
     total_count: number;
}
   export interface IGithubIssueSearchParams {
     sort: string;
     order: 'asc' | 'desc';
     page: number;
     per_page: number;
}

When we call the GitHub issue API. It returns total_count, incomplete_results, and items. In the items section, the list of issues will return. I showed each of the issues 5 values in each row. If you want to show more, you need to modify the columns section which I will explain later.

gridApi: GridApi;
columnApi: ColumnApi;
gridOptions: GridOptions = null;
searchParams: IGithubIssueSearchParams = {
  sort: 'created',
  order: 'desc',
  page: 1,
  per_page: 100
}
githubIssues: IGithubIssue[] = [];
  1. GridApi is an interface that defines the API for interacting with the grid in the ag-Grid.
  2. ColumnApi is an interface that defines the API for interacting with columns in the ag-Grid.
  3. GridOptions is a type that represents the configuration options for the ag-Grid.
  4. searchParams is set as the query params default value. when we open the ag-grid table it will send this value as a query params in the GitHub Issue API. After getting a response from the GitHub API I increment 1 the searchParams page value then When we scroll and go to the bottom of the table then we will send another request with the new searchParams value. All the values will same but the page number will change.
  5. githubIssues, When we get response from the api, we will push the response item in this array.

In the constructor, i added the private _httpClient: HttpClient to call the api. In the ngOnInit function, At first i initialize the gridOptions.

this.gridOptions = {
    getRowNodeId: data => data.id,
    columnDefs: this.columns,
    rowModelType: 'infinite',
    onGridReady: this.onGridReady.bind(this),
    overlayLoadingTemplate: `<span class="ag-overlay-loading-center">No issue found.</span>`
};

Define the column definition. In the columnDefs, The field name will be the exact similar with your api response item key. If it’s not matched then this column value will show empty. As i mentioned before, we use rowModelType: ‘infinite’ because the other server-side rowModelType is only available for the enterprise edition.

 private get columns(): ColDef[] {
  return [
    {
      flex: 1,
      field: 'id',
      headerName: 'ID',
      cellRenderer: (params: ICellRendererParams) => {
        if (params.value !== undefined) {
          return params.value;
        } else {
          return '<img src="https://www.ag-grid.com/example-assets/loading.gif" alt="loading">';
        }
      }
    },
    {
      flex: 1,
      field: 'created_at',
      minWidth: 130,
      tooltipField: 'Created',
      headerName: 'Created',
    },
    {
      flex: 1,
      field: 'state',
      minWidth: 130,
      tooltipField: 'State',
      headerName: 'State'
    },
    {
      flex: 1,
      field: 'number',
      minWidth: 130,
      tooltipField: 'Number',
      headerName: 'Number'
    },
    {
      flex: 1,
      field: 'title',
      headerName: 'Title',
      tooltipField: 'Title',
      minWidth: 140
    }
  ]
}

The onGridReady method will execute when the html rendered. In the onGridReady function, I set the gridApi, columnApi and call the load issue function.

onGridReady(params: GridReadyEvent) {
    this.gridApi = params.api;
    this.columnApi = params.columnApi;
    this.loadGithubIssues();
}

In the loadGithubIssues function, I declared a const variable dataSource. In the dataSource variable, there have two items rowCount and getRows. In the getRows function, we call the GitHub api and load the and then set the response items in the githubIssues variable. This one is declared in the top and set the response data in params successCallback. Before that i increment 1 the page number for getting the next page data when we send another request. The dataSource then set into the gridOptions api. When we go to the bottom of the table then this loadGithubIssues function execute again and then again.

loadGithubIssues() {
  const dataSource: IDatasource = {
    rowCount: undefined,
    getRows: (params: IGetRowsParams) => {
      this.getRepoIssues(this.searchParams).subscribe(response => {
        this.githubIssues = response?.items;
        let lastRow = -1;
        if (response?.items?.length < this.searchParams?.per_page) {
          lastRow = this.githubIssues?.length;
        }
        this.searchParams.page = this.searchParams.page+1;
        params.successCallback(this.githubIssues ?? [], lastRow);
      }, error => {
      });
    },
  };
  this.gridOptions.api.setDatasource(dataSource);
}

The app.component.ts file whole code,

   import {Component, OnInit} from '@angular/core';
   import { ColDef, ColumnApi, GridApi, GridOptions, GridReadyEvent, ICellRendererParams, IDatasource, IGetRowsParams } from "ag-grid-community";
   import {IGithubApi, IGithubIssue, IGithubIssueSearchParams} from './IGithubApi';
   import {Observable} from "rxjs";
   import {HttpClient} from "@angular/common/http";
   
   @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.scss']
   })
   export class AppComponent implements OnInit{
      gridApi: GridApi;
      columnApi: ColumnApi;
      gridOptions: GridOptions = null;
      searchParams: IGithubIssueSearchParams = {
         sort: 'created',
         order: 'desc',
         page: 1,
         per_page: 100
      }
      githubIssues: IGithubIssue[] = [];
      constructor(private _httpClient: HttpClient) {}
   
      ngOnInit() {
         this.gridOptions = {
            getRowNodeId: data => data.id,
            columnDefs: this.columns,
            rowModelType: 'infinite',
            onGridReady: this.onGridReady.bind(this),
            overlayLoadingTemplate: `<span class="ag-overlay-loading-center">No issue found.</span>`
         };
      }
   
      onGridReady(params: GridReadyEvent) {
         this.gridApi = params.api;
         this.columnApi = params.columnApi;
         this.loadGithubIssues();
      }
      
      loadGithubIssues() {
         const dataSource: IDatasource = {
            rowCount: undefined,
            getRows: (params: IGetRowsParams) => {
               this.getRepoIssues(this.searchParams).subscribe(response => {
                  this.githubIssues = response?.items;
                  let lastRow = -1;
                  if (response?.items?.length < this.searchParams?.per_page) {
                     lastRow = this.githubIssues?.length;
                  }
                  this.searchParams.page = this.searchParams.page+1;
                  params.successCallback(this.githubIssues ?? [], lastRow);
               }, error => {
               });
            },
         };
         this.gridOptions.api.setDatasource(dataSource);
      }
   
      getRepoIssues(searchParams: IGithubIssueSearchParams): Observable<IGithubApi> {
         const href = 'https://api.github.com/search/issues';
         const requestUrl = `${href}?q=repo:angular/components&sort=${searchParams?.sort}&order=${searchParams?.order}&page=${searchParams?.page}&per_page=${searchParams?.per_page}`;
         return this._httpClient.get<IGithubApi>(requestUrl);
      }
      
      private get columns(): ColDef[] {
         return [
            {
               flex: 1,
               field: 'id',
               headerName: 'ID',
               cellRenderer: (params: ICellRendererParams) => {
                  if (params.value !== undefined) {
                     return params.value;
                  } else {
                     return '<img src="https://www.ag-grid.com/example-assets/loading.gif" alt="loading">';
                  }
               }
            },
            {
               flex: 1,
               field: 'created_at',
               minWidth: 130,
               tooltipField: 'Created',
               headerName: 'Created',
            },
            {
               flex: 1,
               field: 'state',
               minWidth: 130,
               tooltipField: 'State',
               headerName: 'State'
            },
            {
               flex: 1,
               field: 'number',
               minWidth: 130,
               tooltipField: 'Number',
               headerName: 'Number'
            },
            {
               flex: 1,
               field: 'title',
               headerName: 'Title',
               tooltipField: 'Title',
               minWidth: 140
            }
         ]
      }
   }

In the app.component.html file,

<ag-grid-angular
  *ngIf="gridOptions !== null"
  class="ag-theme-balham-dark"
  [style.height.%]="100"
  [style.width.%]="100"
  [gridOptions]="gridOptions">
</ag-grid-angular>

If you want to see the table in dark mode. Just change the class name. The class name will be ag-theme-balham-dark.

When you run this project in your local environment using the following command:

    npm run start

You will see the data loads from GitHub and show it on the ag-grid table.

Conclusion

Ag-Grid’s server-side infinite scrolling feature is a powerful tool that enables developers to work with large datasets without compromising on performance. By fetching data from the server in smaller batches, as the user scrolls down the page, you can significantly reduce the amount of data that needs to be loaded at once, improving the performance of your application.

Implementing server-side infinite scrolling in Ag-Grid is relatively straightforward, and the library provides extensive documentation and examples to guide you through the process. If you’re working with large datasets in your web application, we highly recommend giving Ag-Grid’s server-side infinite scrolling feature a try.

The whole codes are available in this GitHub repository.

I hosted this project at Netlify. Here is the live link of this project.

Alt text

That’s it. I hope this article helps to create Ag-grid server-side infinite scroll in angular.

I will write a new article where I will implement the ag-grid server-side sorting.

Jobayer

© 2025 Jobayer Ahmed. All rights are reserved.