Search code examples
angularprimengngrxprimeng-turbotable

PrimeNG TurboTable - strange virtual scroll behaviour - keeps doubling event.rows


I've implemented a custom Grid component that uses PrimeNG TurboTable and NgRx and I've added the virtual scroll feature to it using the built-in TurboTable virtual scroll. It is also lazy loading, with onLazyLoad handler. However, I get this strange behaviour when onLazyLoad is called as I am scrolling through the grid results.

onLazyLoad called with event.first=0 and event.rows=40
onLazyLoad called with event.first=0 and event.rows=80
onLazyLoad called with event.first=0 and event.rows=160
onLazyLoad called with event.first=0 and event.rows=320
onLazyLoad called with event.first=0 and event.rows=640
onLazyLoad called with event.first=0 and event.rows=1280

So there are 2 problems:

  1. event.first always stays 0
  2. event.rows keeps doubling to very high values

I thought that it's a bug in PrimeNG but when I've tried to reproduce it with a simple StackBlitz, there it is behaving correctly. https://stackblitz.com/edit/github-primeng-virtualscroll-issue

I see this on StackBlitz:

loadDataOnScroll is called with event.first=0 and event.rows=40
loadDataOnScroll is called with event.first=20 and event.rows=40
loadDataOnScroll is called with event.first=40 and event.rows=40
loadDataOnScroll is called with event.first=80 and event.rows=40
loadDataOnScroll is called with event.first=120 and event.rows=40

So event.rows stays constant as it is supposed to and event.first increases normally.

Looks like something is triggering this from the way I use the TurboTable, but I don't know what. What could be the issue? Here is the relevant code from my grid component:

The lazy load handler:

onLazyLoad(event: LazyLoadEvent) {
    // TODO - In this StackBlitz it behaves correctly
    // https://stackblitz.com/edit/github-primeng-virtualscroll-issue
    console.log(
        `onLazyLoad called with event.first=${event.first} and event.rows=${
            event.rows
        }`
    );

    // TODO - This state should be handled by NgRx as well.
    this.loading = true;
    this.filters = {};

    const hasPagination = this.settings.features.Pagination;

    // TODO - Tweak the behavior of virtual scroll skipCount/maxResultCount
    // based on testing results.
    // The default behavior is a bit strange, skipCount is always 0
    // and maxResultCount keeps getting doubled.
    let pageSize = event.rows ? event.rows : GridConfig.PageSize;
    this.filters.maxResultCount = pageSize;
    this.filters.skipCount = event.first ? event.first : 0;
    this.filters.ignorePagination = !hasPagination;

    if (event.sortOrder && event.sortField) {
        const sortingDirection = event.sortOrder > 0 ? 'ASC' : 'DESC';
        this.filters.sorting = event.sortField + ' ' + sortingDirection;
    }

    if (event.globalFilter) {
        this.filters.filter = event.globalFilter;
    }

    if (event.filters) {
        this.filters.columnFilters = this.buildColumnFilters(event.filters);
    }

    this.filters.parentFilters = this.parentFilters; // this works only with client-side caching & filtering

    if (this.settings.features.ClientSideCaching) {
        // Load only once
        this.gridLoaded().subscribe(loaded => {
            if (loaded) {
                this.store.dispatch(
                    new this.settings.stateActions.FilterClientSide(
                        this.filters
                    )
                );
            }
        });
    } else {
        this.store.dispatch(
            new this.settings.stateActions.Read(this.filters)
        );
    }
}

The parameters I pass in the template:

<p-table
        #dataTable
        [value]="(data$ | async)?.items"
        [loading]="loading"
        [lazy]="true"
        [virtualScroll]="!settings.features.Pagination"
        [virtualRowHeight]="35"
        [scrollable]="!settings.features.Pagination"
        (onLazyLoad)="onLazyLoad($event)"
        [autoLayout]="true"
        [columns]="selectedColumns"
        [paginator]="settings.features.Pagination"
        scrollHeight="400px"
        [resizableColumns]="true"
        [rowsPerPageOptions]="[10, 20, 30]"
        [totalRecords]="(data$ | async)?.totalCount"
        [rows]="(this.settings.states.Search | async)?.maxResultCount"
        [stateStorage]="this.settings.persistence?.stateStorage"
        [stateKey]="this.settings.persistence?.stateKey"
        [(selection)]="selectedRows"
        [selectionMode]="this.settings.rowSelection?.selectionMode || null"
        (onRowSelect)="onRowSelect($event)"
        [dataKey]="this.settings.rowSelection?.dataKey || null"
    >

Any help or ideas would be much appreciated! Thanks!


Solution

  • The problem happened because event.rows is persisted in the NgRx store and I was reusing it when building the filters and every time onLazyLoad gets called it would get doubled.

    Here I initialize rows with the NgRx store value:

    [rows]="(this.settings.states.Search | async)?.maxResultCount"
    

    Then inside onLazyLoad I was using event.rows:

    let pageSize = event.rows ? event.rows : GridConfig.PageSize;
    this.filters.maxResultCount = pageSize;
    

    PrimeNG doubles event.rows in their code for virtual scroll and it gets persisted into the NgRx store when I call FilterClientSide action. Then the template gets that new value and on a subsequent onLazyLoad it gets doubled again.

    The solution that worked for me was to just set a constant for this:

    if (this.settings.features.VirtualScroll) {
        pageSize = GridConfig.PageSize * 2;
    } else {
        pageSize = event.rows ? event.rows : 10;
    }