Search code examples
infinite-scrollag-gridag-grid-ng2

Ag Grid cell renderer not instantiated until scrolled back in view


I noticed odd behaviour when using ag-grid with infinite scroll and cell renderers. When data is received from the datasource, the cell renderers appear to not be instantiated. When scrolling down until the row is out of view, and scrolling back up, the cell renderers are instantiated. I would assume that the cell renderers are instantiated immediately on the loading of the row, am I encountering a bug, or am I missing something?

I'm currently using ag-grid community version to display a read-only collection. This data is served from a server which handles search and filtering. I'm using Angular 5.2.0, with ag-grid 18.0.1 and ag-grid-angular 18.0.1. This behaviour also appeared with ag-grid version 17.10.0.

I've tried both cellRenderer: Function(Params), and I've tried both AgRendererComponent and ICellRendererAngularComp. They all only call agInit(Params) when scrolling back up to the row.

So, is this a bug or intended behaviour, if it is a bug, is there a known work-around? Thank you for your time, here is my code:

The component which retrieves an answer to a question from a map provided, (the 'field' in the column definition is the id for the question)

import { Component, ViewContainerRef } from '@angular/core';
import { AgRendererComponent } from 'ag-grid-angular';

@Component({
    selector: 'editor-cell',
    templateUrl: './answer.component.html'
})
export class AnswerComponent implements AgRendererComponent {

    private answer: any;

    private getAnswer(params: any): string {
        if(params.data == null){
        return;
        }
        if(params.data.answers == null){
            return;
        }
        return params.data.answers[params.colDef.field]
    }

    agInit(params:any):void {
        this.answer = this.getAnswer(params);
        if(this.answer != null){
            console.log("Set on init", this.answer);
        }
    }

    refresh(params:any):boolean {
        this.answer = this.getAnswer(params);
        if(this.answer != null){
            console.log("Set on refresh", this.answer);
        }
        return true;
    }
}

Here is my column definitions: (Note: the non-cell renderers do appear immediately, only the cellRenderer Function and cellRendererFramework do not immediately load.

{
                headerName: 'messages sent',
                field: 'sentMessages',
                suppressFilter: true
            },
            {
                headerName: 'work experience',
                field: '57ceb64efb6b510b857e8a25',
                cellRenderer: function(params){
                    return params.value;
                    if(params.node == null){
                        return ''
                    }
                    if(params.node.data == null){
                        return ''
                    }
                    if(params.node.data.answers == null){
                        return ''
                    }
                    if(params.node.data.answers[params.colDef.field] == null){
                        return ''
                    }
                    return params.node.data.answers[params.colDef.field].nl;
                }
            },
            {
                headerName: 'education',
                field: '57ceb64efb6b510b857e8a26',
                cellRendererFramework: AnswerComponent
            },
            {
                headerName: 'job title',
                field: '5a941e258bdfc300143cf6b0',
                cellRendererFramework: AnswerComponent
            }

The datasource

this.datasource = {
    rowCount: null,
    getRows: function(params) {
        ts.query({
            page: Math.floor(params.startRow / 100),
            size: 100,
            filter: JSON.stringify(params.filterModel)
        }).subscribe(
            (res: HttpResponse<any[]>) => {
                let data = res.body;
                let lastRow = -1;
                if (data.length <= params.endRow) {
                    lastRow = data.length;
                }
                params.successCallback(data, -1);
            },
            (res: HttpErrorResponse) => console.log(res.message));
    }
};
this.rowSelection = "multiple";
this.rowModelType = "infinite";
this.maxBlocksInCache = 2;
this.infiniteInitialRowCount = 200;
this.maxConcurrentDatasourceRequests = 2;

Component HTML

<div>
    <ag-grid-angular
        #agGrid
        style="width: 100%; height: 500px;"
        id="myGrid"
        [rowData]="rowData"
        class="ag-theme-balham"
        [columnDefs]="columnDefs"
        [datasource]="datasource"
        [rowModelType]="rowModelType"
        [maxBlocksInCache]="maxBlocksInCache"
        [infiniteInitialRowCount]="infiniteInitialRowCount"
        [maxConcurrentDatasourceRequests]="maxConcurrentDatasourceRequests"
        [enableServerSideFilter]="true"
        [floatingFilter]="true"
        (gridReady)="onGridReady($event)"
        [newRowsAction]="keep"
    ></ag-grid-angular>
</div>

Solution

  • cellRenderer is called only when the cell is scrolled into the view.

    As documented in this blog: 8 Performance Hacks for JavaScript

    When you scroll in ag-Grid, the grid is doing row and column virtualisation, which means the DOM is getting trashed. This trashing is time consuming and if processed within the event listener will make the scroll experience 'rough'.

    To get around this, the grid uses debouncing of scroll events with animation frames. This is a common trick to achieve smooth scrolling and is explained very well in this blog Leaner, Meaner, Faster Animations with RequestAnimationFrame. As this technique is well explained in posts such as above, I won't repeat it here. Suffice to say, we found this delivers a good performance boost.

    I would also recommand you to go through this document regarding Performance provided by