bunt
bunt

Reputation: 312

How to maintain vertical scroll when updating Angular 5 data table?

I'd like to frequently update my data table (Covalent td-data-table with several ng-template) with new data pulled from a JSON REST API. More rows than will fit on browser so user may need to scroll vertically. But when I update the data in the table it redraws completely i.e. the vertical scroll position resets to the top, tool tips flash, etc..

Hacks to e.g. save/restore the vertical scroll such as below kind of work but they create a lot of visual mess, especially in Firefox.

// save vertical scroll
this.scrollTop = this.tableElt.nativeElement.querySelector('.td-data-table-scrollable').scrollTop;

// update table data here
this.data = newData;

// restore vertical scroll
setImmediate(() => {
    this.tableElt.nativeElement.querySelector('.td-data-table-scrollable').scrollTop = this.scrollTop;
  }
});

How can I cleanly update the data in a table (or any component really) without hacking to reset scroll positions & putting up with a lot of flashing behaviour?

If there is no solution using the Covalent data table, is there another Angular 2+ control that handles this properly?

Animated screen capture of problem: Vertical scroll snaps back when data is updated. Vertical scroll should be maintained across data updates. screencapture

Upvotes: 11

Views: 12774

Answers (4)

Gowtham Raj J
Gowtham Raj J

Reputation: 967

Pushing data to the same array variable which is being used by *ngFor wont re-render html.

Simple example: https://angular-frjdnf.stackblitz.io

Edit:

I have made a sample project with covalent table using *ngFor with angular updating only the additional row and not re-rendering the whole table.

Link to Stackblitz.io sample project.

HTML

<button (click)="addData()">Add Data</button>
<table td-data-table>
    <thead>
    <tr td-data-table-column-row>
      <td td-data-table-column *ngFor="let column of columns">
        {{column.label}}
      </td>
    </tr>
    </thead>
    <tbody>
    <tr td-data-table-row *ngFor="let row of basicData">
      <td td-data-table-cell *ngFor="let column of columns">
        {{ row[column.name] }}
      </td>
    </tr>
    </tbody>
  </table>

TS

basicData has the initial data that was loaded into the table and I am just pushing some dummy data on click of the "Add Data" button to the basicData for the purpose of testing the scroll bar.

import { Component } from '@angular/core';
import { ITdDataTableColumn } from '@covalent/core/data-table';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  columns: ITdDataTableColumn[] = [
    { name: 'first_name',  label: 'First Name' },
    { name: 'last_name', label: 'Last Name' },
    { name: 'gender', label: 'Gender' }
  ];

  count = 0;

  basicData: any[] = [
    {
      "first_name": "Sully",
      "gender": "Male",
      "last_name": "Clutterham"
    },
    ...
  ]

  additionalData: any[] = [
    {
      "first_name": "Sully",
      "gender": "Male",
      "last_name": "Clutterham"
    },
    ...
  ]

  addData(){
    if(this.count >= this.additionalData.length)
      this.count = 0;

    this.basicData.push(this.additionalData[this.count]);
    this.count++;
  }

}

Hope this helps.

Upvotes: 1

Pearman
Pearman

Reputation: 1068

You could try using a trackBy function. This function will be used to determine what rows of your *ngFor have changed to optimize redraws.

<tbody>
<tr td-data-table-row *ngFor="let row of basicData; trackBy: getRowId">
  <td td-data-table-cell *ngFor="let column of columns" [numeric]="column.numeric">
    {{column.format ? column.format(row[column.name]) : row[column.name]}}
  </td>
  <td td-data-table-cell (click)="openPrompt(row, 'comments')">
    <button mat-button [class.mat-accent]="!row['comments']">{{row['comments'] || 'Add Comment'}}</button>
  </td>
</tr>
</tbody>

And then in your Typescript:

getRowById(index, row) {
   // Return some unique primitive idenitifier (string, number, etc.)
   return row['some unique property'];
}

For more info on trackBy check out:

https://netbasal.com/angular-2-improve-performance-with-trackby-cc147b5104e5 https://blog.angular-university.io/angular-2-ngfor/

Also, NGX-Datatable works very well for this use-case. It has built in virtual scrolling. https://github.com/swimlane/ngx-datatable

Upvotes: 9

steve biko
steve biko

Reputation: 171

I suggest you switch to angular material and bind it to an observable data source.

https://material.angular.io/components/table/overview#observable-stream-of-data-arrays

When the datasource(Observable) is updated with new data it will update the DOM and there will be no need to follow scroll events.

In case you are worried about the number on items listed on a page supports pagination in a simple way; . https://material.angular.io/components/table/overview#pagination

SideNote: Switch to angular material as their components are well documented, with examples and sample codes Let me know in case you get stuck.

Upvotes: 2

Pradeep
Pradeep

Reputation: 140

It looks like when you get more data, you are refreshing whole list(array used in *ngFor) so angular is re-drawing full table again. Try to push only difference data to that array.

Upvotes: 0

Related Questions