Reputation: 312
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.
Upvotes: 11
Views: 12774
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
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
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
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