11

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

bunt
  • 302
  • 2
  • 13
  • 25
  • hello, i think you need ngx-infinite-scroll , it works perfectly, you can use the (scrolled) to trigger you request and get new data and you will not lose your scroll position, here is an example using it: http://echoesplayer.com/#/search/videos – Fateh Mohamed Apr 13 '18 at 13:51
  • At the `ngFor` you should use for the list, Do you use the trackBy function? Do you do it through the indices? But you need it for this guy. If you put the code in question it will be much easier to help you. – botika Apr 16 '18 at 01:50
  • @FatehMohamed it's not clear to me how I might use ngx-infinite with td-data-table. – bunt Apr 20 '18 at 07:01
  • @botika trackBy sounds like we're on the right track, but td-data-table does not have an ngFor. I can't find any way to use trackBy with td-data-table. – bunt Apr 20 '18 at 07:02
  • 1
    After a cursor glance, `` ref [covalent-nightly/virtual-scroll/](https://github.com/Teradata/covalent-nightly/tree/master/virtual-scroll) - can you wrap your table in a virtual scroll? – Richard Matsen Apr 20 '18 at 08:11
  • @RichardMatsen td-virtual-scroll-container supports only lists, it can neither contain or be contained in a table. Not sure who/why would upvote this answer. – bunt Apr 20 '18 at 11:13

4 Answers4

9

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

Pearman
  • 1,068
  • 5
  • 14
  • I'm using td-data-table not ngx-datatable. trackBy may be an answer with ngx-datatable but it's not clear where/how to use with td-data-table. – bunt Apr 20 '18 at 07:03
  • Also @Pearman your code uses td-data-table-row/cell/etc. Question as stated uses td-data-table and ng-template, hence no *ngFor, hence no trackBy. – bunt Apr 20 '18 at 11:21
  • So, ***is there another Angular 2+ control that handles this properly*** - this is ok, but the correct syntax for `td-data-table` is not ok? –  Apr 21 '18 at 08:05
  • Not sure what you mean @eric99. I'd be very happy to use td-data-table-row/cell/etc. as demonstrated above, but the functionality isn't equivalent. For example, no sticky headers, no ng-templates so you can't mix & match columns into tables with hacking *ngIf, etc.. But td-data-table seems to fall flat when used w/ vertical scrolling, hence the purpose of the original question. – bunt Apr 24 '18 at 00:12
2

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.

steve biko
  • 171
  • 5
  • the angular material data table is very primitive, most egregiously it will not stick table headers in place, they scroll when you scroll the data! That is one of the main reasons I need td-data-table which can stick the table headers in place while the data is scrolled. – bunt Apr 20 '18 at 07:09
  • The sticky header issue was solved. https://github.com/angular/material2/issues/9143 Use suggested CSS `.mat-header-row { position: sticky; position: -webkit-sticky; top: 0; background-color: inherit; }` – steve biko Apr 20 '18 at 08:23
  • thanks I didn't realize that, that may be a direction forward. Still I have dozens of tables I'd like to avoid rewriting if possible, question is about td-data-table not mat-table. – bunt Apr 20 '18 at 11:19
  • In end I went with @stevebiko's approach and switched to native angular material table. works ok, still supports templates, sticky workaround works well. I ditched td-data-table, never got it working to satisfaction. sorry happened too late for the bounty. – bunt May 31 '18 at 03:48
1

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.

Gowtham Raj J
  • 937
  • 9
  • 26
  • there is no *ngFor, see Basic Data Table code at (https://teradata.github.io/covalent/#/components/data-table). – bunt Apr 20 '18 at 11:14
  • From the given demo page, If you check "Custom Data Table" Demo code, they have got `......`, and they are passing the data from Json file to basicData. I guess you are following the "Basic Data Table" Demo structure, where you need to pass only the data through the input decorator [data] of `` directive. Could you try to use the "Data Table (Atomic) Components" type (as mentioned at the end of page) ?
    – Gowtham Raj J Apr 20 '18 at 11:53
  • From "Custom Data Table" Code section, they are using the "Data Table Components", where they are using custom attributes like `td-data-table-row`, `td-data-table-cell`. ` ...
    {{ row[column.name] }}
    `
    – Gowtham Raj J Apr 20 '18 at 12:02
  • I'd like to use atomic components but then I lose all the features of td-data-table e.g. automatic sticky headers. Question is specifically for td-data-table with ng-template children, not td-data-table, td-data-table is what I need to get working. – bunt Apr 21 '18 at 02:14
0

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.

Pradeep
  • 140
  • 9
  • 1.-Use ViewChild to get the "table". 2.-On ngDestroy (or before refresh the table) save the position of naviteElement scroll in a service, localhost or variable.3.- when update the table use this saved variable – Eliseo Apr 17 '18 at 15:08
  • @Eliseo that's what the sample code I pasted does. As mentioned it creates a lot of flashing & redraw, especially in Firefox. – bunt Apr 20 '18 at 07:05