PrimeNG Lazy load data with async pipe
Asked Answered
E

3

5

I have a very large amount of data (400.000 records) that I need to show in a PrimeNG data table. For this I need a lazy loading table, since you cannot load all data into the table in one time (this will make your browser crash).

For creating the table I am using the following technologies:


What do I want

I am trying to create a lazy loading table as shown in the PrimeNG docs, where the data is loaded from the server and shown in table. When the user navigates to the next tab the next x amount of data is loaded and shown.

The only difference is that I fetch all the data from the server before giving it to the table component. This way I will only have to pick certain data from the datasource and show it to the user.


The problem

While trying to implement it I ran into the problem that the (onLazyLoad) function is only called once, in the onInit() phase, before the data is loaded from the server.

I can undo this by adding [lazyLoadOnInit]="false", but this results in the lazy load function not being called at all. I was hoping I could trigger the load function by changing the [totalRecords] property when the data is loaded, but this also doesn't trigger the function.

I cannot find any other function in the PrimeNG p-table code that I could use for triggering the (onLazyLoad), or am I missing something?


Code

public ngOnInit(): void {
  this.cars$ = this.carService.entities$; // = []
  this.carService.getAll(); // = [Car, Car, Car, Car] OR []
}

this.carService.entities$ has a default value of [] and is populated with the result of the getAll() function (this can also be [] if there are no results)


I have reproduced my problem in a StackBlitz. Here you can see that the data is never shown, because the (onLazyLoad) is only called the first time, when the data is empty.

Note that I am using the Angular Async pipe for passing the data into my component. This means I need to check the changes in a ngOnChanges() function.

Eakin answered 15/3, 2019 at 13:39 Comment(1)
it 's seem you need to change the change detect to ChangeDetectionStrategy.OnPush when you use async pipe when you use blog.angularindepth.com/…Rigney
R
10

Just update the app.template like this

<ng-container *ngIf="cars$ | async as data">
  <table-component [data]="data"></table-component>
</ng-container>

It seems p-table lasyload dosn't trigger when the data change, even when the data property change from undefined to object(arry)

stackblitz

Updated

Without async pipe get the data

  public ngOnInit(): void {
    this.carService.getAll().subscribe(data => this.data = data);
  }

ngOnChanges method

  public ngOnChanges(change: SimpleChanges): void {
    if(change.data) {
      if (change.data.currentValue) {
      this.datasource = change.data.currentValue;
      this.totalRecords = Array.isArray(change.data.currentValue) ? change.data.currentValue.length : 0;
      this.cars = this.datasource.slice(0, 10); // row number
      this.loading = false;
      }
    }
  }

check this article explains how to use async pipe and change detection

stackblitz

Rigney answered 15/3, 2019 at 16:1 Comment(1)
Thank you for your answer! When testing I noticed that my code is a little bit different then my first example. This makes it a bit harder to work with, since the first value is an empty array instead of undefined. I've updated the stackblitz.com/edit/angular-z52que. I can no longer use your first solution, since it is never undefined and it may return an empty array if there is no data in the database (I should show an empty table in this case). Your second solution is something I would like to avoid, since you can use async with a normal PrimeNG table. Is there an other solution?Eakin
E
2

Thanks to malbarmawi I managed to update the table with new records. The only problem that still persists is that lazy loading was only triggered onInit() of the table. This was too early, since the data has not yet been loaded.

So I needed to find a way to trigger lazy loading. I noticed that the methods of the table are public, so I could inject the table as a @ViewChild and trigger the lazy loading myself.

/**
 * A reference to the primeng table. Since all of it's methods are public we can
 * directly access the methods we need to trigger lazy loading correctly.
 */
@ViewChild(Table)
private tableRef: Table;

public ngOnChanges(change: SimpleChanges): void {
   if(change.data && change.data.currentValue) {
     this.datasource = change.data.currentValue;
     this.totalRecords = Array.isArray(change.data.currentValue) ? change.data.currentValue.length : 0;

     // Trigger lazy loading
     this.tableRef.onPageChange({ first: 0, rows: this.rows });
   }
}

Since I now have lazy loading I can also implement a virtual scroller to improve the table's performance and make it possible to work with my 400.000 records.

For this I only needed to update the tables properties to (note the virtual- properties):

<p-table [columns]="cols" [value]="cars" [scrollable]="true" [rows]="rows" [scrollHeight]="scrollHeight" [virtualRowHeight]="rowHeight" [virtualScroll]="true" [lazy]="true" (onLazyLoad)="loadCarsLazy($event)"  [totalRecords]="totalRecords" [loading]="isLoading"></p-table>

A fully working example of this code can be found on stackblitz

Eakin answered 19/3, 2019 at 12:33 Comment(0)
S
0

Another solution (adapted from here) is to define the following helper functions :

/**
 * Pipe operator wich detects when a loading starts and ends.
 */
export function interceptLoading<T>(onstart: () => void, onstop: () => void): (source: Observable<T>) => Observable<T> {
    return (source$: Observable<T>): Observable<T> =>
        source$.pipe(
            prepare(onstart),
            finalize(onstop)
        );
}

/**
 * Operator which invokes a callback upon subscription
 */
function prepare<T>(callback: () => void): (source: Observable<T>) => Observable<T> {
    return (source$: Observable<T>): Observable<T> =>
        defer(() => {
            callback();
            return source$;
        });
}

And then to use like this in your component :

loading = true;

[...]

return this.service.getAll$().pipe(
     interceptLoading(() => this.loading = true, () => this.loading = false),
     map(...)
 );

And then, in you primeng-table :

<p-table [loading]="loading" ...>
Sfumato answered 24/2, 2023 at 11:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.