1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.
  2. Anuncie Aqui
    Anuncie aqui você Também: fdantas@4each.com.br

onLazyLoad method triggers multiple times on initial load

Discussão em 'Angular' iniciado por Sahil Agarwal, Novembro 21, 2025.

  1. I'm working with a PrimeNG p-table that uses lazy loading and virtual scrolling in my Angular application. However, I've encountered an issue where the onLazyLoad method is being triggered multiple times (two or three times) on the initial load instead of just once. I don't get the totalRecords from backend,can that be causing this issue

    Here’s a simplified version of my code:


    <p-table
    #dt
    class="lme"
    [ngClass]="{
    'no-records': myRecords?.length === 0,
    'few-records': myRecords?.length > 0 && myRecords?.length <= 2,
    'small-dataset': myRecords?.length > 2 && myRecords?.length < 6
    }"
    [scrollable]="true"
    scrollHeight="calc(100vh - 180px)"
    [style]="{ width: '100%' }"
    [columns]="selectedColumns"
    [reorderableColumns]="true"
    [resizableColumns]="true"
    [virtualScroll]="true"
    [virtualRowHeight]="virtualRowHeight"
    [virtualScrollItemSize]="virtualRowHeight"
    [virtualScrollDelay]="200"
    [rows]="pageSize"
    [value]="myRecords"
    [lazy]="true"
    (onLazyLoad)="onLazyLoad($event)"
    [responsive]="true"
    [(selection)]="selectedMyRecords"
    (onRowSelect)="rowClick($event.originalEvent, $event.data)"
    dataKey="id"
    >
    <ng-template pTemplate="caption">
    <div style="text-align: left">
    <p-multiSelect
    [options]="columns"
    [(ngModel)]="selectedColumns"
    optionLabel="columnValue"
    selectedItemsLabel="{0} columns selected"
    [style]="{ minWidth: '200px' }"
    defaultLabel="Choose Columns"
    >
    </p-multiSelect>
    <button
    id="saveDefaultView"
    class="ml-2 btn btn-primary save-default-view"
    (click)="saveDefaultView()"
    >
    Save Default View
    </button>
    </div>
    </ng-template>
    <ng-template pTemplate="header" let-columns>
    <tr>
    <th
    *ngIf="
    user.stakeHolder === 'METAL_OWNER' ||
    user.stakeHolder === 'LME_MEMBER'
    "
    >
    <p-tableHeaderCheckbox
    wcag-label
    (click)="onHeaderSelect($event)"
    ></p-tableHeaderCheckbox>
    </th>
    <th
    [ngClass]="{
    'non-checkbox-col': !col.checkboxSelect,
    'checkbox-col': col.checkboxSelect
    }"
    *ngFor="let col of columns"
    pReorderableColumn
    pResizableColumn
    >
    <span
    [attr.cy-test]="col.columnValue"
    id="{{ col.columnValue }}"
    >{{ col.columnValue }}</span
    >
    <!-- <p-sortIcon
    id="{{ col.field }}"
    [field]="col.sortableColumnName"
    *ngIf="col.sortableColumnName !== 'NONE'"
    ></p-sortIcon> -->
    </th>
    </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
    <tr [pSelectableRow]="rowData">
    <td
    *ngIf="
    user.stakeHolder === 'METAL_OWNER' ||
    user.stakeHolder === 'LME_MEMBER'
    "
    >
    <p-tableCheckbox [value]="rowData" wcag-label></p-tableCheckbox>
    </td>
    <td *ngFor="let col of columns" class="ui-resizable-column">
    <ng-container
    *ngIf="
    col.columnKey !== 'CHEMICAL_COMPOSITION';
    else CHEMICAL_COMPOSITION
    "
    >
    <ng-container
    *ngIf="
    col.columnKey !== 'ORIGINAL_COS_DOCUMENTS';
    else DOWNLOAD_COA
    "
    >
    <!-- <ng-container
    *ngIf="!(col.columnKey === 'COA_DATE' || col.columnKey === 'DATE_OF_PRODUCTION'); else COA_DATE"> -->
    <ng-container
    *ngIf="
    col.columnKey !== 'VALUE_ADD_THEMES_DISCLOSURE';
    else VALUE_ADD_THEMES_DISCLOSURE
    "
    >
    <ng-container *ngIf="col.columnKey !== 'CBAM'; else CBAM">
    <ng-container
    *ngIf="
    col.columnKey !== 'SCR_COMPLIANT';
    else SCR_COMPLIANt
    "
    >
    {{ rowData[col.columnHeaderInSearchResult] }}
    </ng-container>
    </ng-container>
    </ng-container>
    <!-- </ng-container> -->
    </ng-container>
    </ng-container>

    <ng-template #CHEMICAL_COMPOSITION>
    <span
    *ngIf="rowData.recordType === 'ENHANCED'; else BASIC"
    class="link"
    role="button"
    tabindex="0"
    (click)="viewComposition($event, rowData)"
    (keyup)="viewComposition($event, rowData)"
    >View Chemical Composition</span
    >
    </ng-template>
    <ng-template #BASIC> NA </ng-template>

    <ng-template #DOWNLOAD_COA>
    <span
    role="button"
    tabindex="0"
    (click)="exportCoa($event, rowData)"
    (keyup)="exportCoa($event, rowData)"
    class="link"
    >Export Original COA</span
    >
    </ng-template>
    <ng-template #COA_DATE>
    {{ col.columnHeaderInSearchResult }}
    {{
    rowData[col.columnHeaderInSearchResult]
    | date : "dd MMMM, yyyy"
    }}
    </ng-template>
    <ng-template #VALUE_ADD_THEMES_DISCLOSURE>
    <span
    class="link"
    role="button"
    tabindex="0"
    *ngIf="rowData.themesAttached"
    (click)="viewValueAddThemes($event, rowData)"
    (keyup)="viewValueAddThemes($event, rowData)"
    >Details</span
    >
    <span *ngIf="!rowData.themesAttached">NA</span>
    </ng-template>

    <ng-template #CBAM>
    <span
    *ngIf="rowData.recordType === 'ENHANCED'; else BASIC"
    class="link"
    role="button"
    tabindex="0"
    (click)="exportCbam($event, rowData)"
    (keyup)="exportCbam($event, rowData)"
    >Export CBAM report</span
    >
    </ng-template>
    <ng-template #SCR_COMPLIANt>
    {{ rowData[col.columnHeaderInSearchResult] }}
    <span
    *ngIf="rowData.remarks"
    class="link"
    tabindex="0"
    role="button"
    ><i
    class="pi pi-info-circle ml-2"
    tabindex="0"
    role="button"
    aria-label="Info"
    (click)="viewScrComplianceInfo($event, rowData)"
    (keyup)="viewScrComplianceInfo($event, rowData)"
    ></i
    ></span>
    </ng-template>
    </td>
    </tr>
    </ng-template>
    <ng-template pTemplate="emptymessage" let-columns>
    <tr>
    <td [attr.colspan]="selectedColumns?.length">No records found</td>
    </tr>
    </ng-template>
    <ng-template pTemplate="loadingbody">
    <tr>
    <td [attr.colspan]="selectedColumns?.length + 1">
    <div class="loading-text">Loading records...</div>
    </td>
    </tr>
    </ng-template>
    </p-table>




    onLazyLoad(event) {
    if (!this.isManualTriggerActive) {
    this.lastNaturalLazyLoadTime = Date.now();

    if (this.scrollMonitorTimeout) {
    clearTimeout(this.scrollMonitorTimeout);
    this.scrollMonitorTimeout = null;
    }
    }

    if (this.lazyLoadTimeout) {
    clearTimeout(this.lazyLoadTimeout);
    }

    if (this.loading || this.isProcessingLoad) {
    const timeSinceLastCall = Date.now() - this.lastApiCallTime;
    if (timeSinceLastCall > 30000) {
    this.loading = false;
    this.isProcessingLoad = false;
    } else {
    return;
    }
    }

    if (!this.hasMoreData) {
    return;
    }

    if (event.sortField) {
    let sortKey = event.sortField;
    let sortCol;
    let colIndex = this.defaultView.findIndex(
    (col) => col.columnHeaderInSearchResult === sortKey
    );
    colIndex !== -1 && (sortCol = this.defaultView[colIndex]["columnKey"]);
    this.filters["columnSortStatus"] = {};
    this.filters["columnSortStatus"][sortCol] =
    event.sortOrder === 1 ? "ASC" : "DESC";

    // Reset for new sort
    console.log("Sort changed, resetting data");
    this.myRecords = [];
    this.currentPage = 0;
    this.hasMoreData = true;
    this.isProcessingLoad = true;
    this.loading = true;
    this.totalRecords = 0;

    this.filters["keyword"] = this.filters["freeText"];
    this.getSearchDataVirtual(this.filters);
    return;
    }

    // For initial load (when no data loaded yet)
    if (this.myRecords.length === 0) {
    console.log("Initial load");
    this.isProcessingLoad = true;
    this.loading = true;
    this.filters["keyword"] = this.filters["freeText"];
    this.getSearchDataVirtual(this.filters);
    return;
    }

    // For virtual scroll, check if we need to load more data
    // Adaptive loading based on dataset size
    const visibleEndIndex = event.first + event.rows;

    // Use smaller buffer for smaller datasets to ensure loading triggers
    const adaptiveBufferPercent = this.myRecords.length < 200 ? 0.2 : 0.5;
    const bufferSize = Math.max(
    10,
    Math.floor(this.pageSize * adaptiveBufferPercent)
    );
    const loadThreshold = this.myRecords.length - bufferSize;

    if (visibleEndIndex < loadThreshold) {
    console.log("Skipping load - not approaching end of loaded data");
    return;
    }

    const currentTime = Date.now();
    const timeSinceLastCall = currentTime - this.lastApiCallTime;
    const timeNeeded = Math.max(
    0,
    this.minTimeBetweenCalls - timeSinceLastCall
    );

    if (this.lazyLoadTimeout) {
    clearTimeout(this.lazyLoadTimeout);
    }

    const totalDelay = Math.max(150, timeNeeded);

    this.lazyLoadTimeout = setTimeout(() => {
    if (this.hasMoreData && !this.loading && !this.isProcessingLoad) {
    this.isProcessingLoad = true;
    this.loading = true;

    this.filters["keyword"] = this.filters["freeText"];

    this.getSearchDataVirtual(this.filters);
    }
    }, totalDelay);
    }



    Observations:

    • The onLazyLoad method is called multiple times during the initial load.
    • I have implemented guards (isProcessingLoad and loading), but they don’t seem to prevent the multiple calls.
    • I am using both lazy loading and virtual scrolling features of PrimeNG.

    Questions:

    • What could be causing the onLazyLoad method to trigger multiple times on initial load?
    • How can I effectively prevent this behavior while still allowing legitimate lazy loading to occur when the user scrolls?

    Additional context:

    • I am using PrimeNG version 14 with Angular version 14
    • The table is configured to support virtual scrolling, and the data is loaded from an API.

    Continue reading...

Compartilhe esta Página