swimlane / ngx-datatable

✨ A feature-rich yet lightweight data-table crafted for Angular
http://swimlane.github.io/ngx-datatable/
MIT License
4.63k stars 1.68k forks source link

Table not displaying after a vertical scroll and then a change to different columns and rows. #647

Closed professorjoshua closed 7 years ago

professorjoshua commented 7 years ago

[ x ] bug report => search github for a similar issue or PR before submitting [ ] feature request [ ] support request => Please do not submit support request here

Current behavior

Create a drop down list that allows a user to select an object (a different table from the database to display). When the user selects the object change the column headings, call the API to retrieve the appropriate data. Up until now everything works. Even if one object has 10 rows and a second object has 1000 rows. Now set the vertical scroll to true (to increase the performance). Switching between objects still works partially. If you select the object with 1000 items, scroll towards the bottom of the table and then select the object with less rows you will see that the table is not repainted unless you scroll. You may have to resize the browser window to get a scroll bar.

Expected behavior

When you change the columns and rows (by selecting a different object) then table should be repainted regardless of where you were scrolled to in the previous table.

Reproduction of the problem

What is the motivation / use case for changing the behavior?

The vertical scroll is needed to increase the performance. Displaying the table of a 1100 rows of 5 columns took 6 to 10 seconds (depending on the browser) without the vertical scroll. With the vertical scroll the table was rendered in 1 second.

Please tell us about your environment:

Windows 10 Enterprise, Visual Studio 2015, IIS

adammedford commented 7 years ago

I thought about this an as an interim solution, try adding *ngIf='areColumnsInitialized' and then

areColumnsInitialized: boolean = false;

changeColumns(newColumns) {
this.areColumnsInitialized = false;
this.columns = newColumns;
this.areColumnsInitialized = true;
}

The idea being that the grid is briefly destroyed and recreated when you change columns. I suspect that is your issue so you wouldn't have to wait on all the rows.

professorjoshua commented 7 years ago

Great minds think alike, however, I already tried that to no avail. In my code I used a Boolean called scrollbarV. I tried to change the scrollbarV property to true and false. I also tried the *ngIf based on the Boolean. The setting to false and true were placed around the assignment of columns.

    <ngx-datatable *ngIf="scrollbarV==true" #myTable style="height:400px;" 
                   [rows]="datarows"
                   [columnMode]="'standard'"
                   [columns]="columnproperties"
                   class='material expandable'
                   [selected]="selected"
                   [selectionType]="'single'"
                   [messages]="messages"
                   (activate)="onActivate($event)"
                   (select)='onSelectItem($event)'
                   [loadingIndicator]="loadingIndicator"
                   [scrollbarV]="scrollbarV"
                   [rowHeight]="50"   
                   id="lookupdata">         
    </ngx-datatable>
ipassynk commented 7 years ago

Try to add setTimeout(() => window.dispatchEvent(new Event('resize'))); after new set is selected.

professorjoshua commented 7 years ago

Tried setTimeout(() => window.dispatchEvent(new Event('resize'))); after the new set of data is selected but I still have the same problem. It appears the ngx-datatable is stuck in the position I scrolled to from the previous dataset. I even tried to use scrollTop() but have not had any success.

enough1987 commented 7 years ago

I have the same issue, when I go to another page (the same component is going ) I see white space .

     <ngx-datatable 
        #table
        class="material"
        [rows]="rows"
        [columns]="columns"
        [selected]="selected"
        [selectionType]="'multi'"
        [columnMode]="'force'"
        [headerHeight]="24"
        [footerHeight]="0"
        [rowHeight]="40"
        [scrollbarV]="true"
        [scrollbarH]="false"
        [messages]="{emptyMessage: 'No items have been added to the collection'}"
        (select)='onSelect($event)' 
        (page)='onPage($event)'
        (recalculate)=recalculate($event) >

         <ng-template #editTmpl let-value="value" let-row="row" let-i="index" >

          <span title="Double click to edit" 
                  *ngIf="!editing[row.$$index + '-name']">
                  {{value}}
            </span>
            <input #input autofocus type="text"
                  (blur)="updateValue($event, 'name', value, row, input)" 
                  (keyup.enter)="updateValue($event, 'name', value, row, input)"
                  [hidden]="!editing[row.$$index + '-name']" 
                  [value]="value"
                  [class.error]="row.error"
            />
            <img class="rename-icon" src="assets/images/rename.png" 
                  (click)="onRename( input, row )"
                  [hidden]="!checkingPermission(row, 'Update')"   />
         <!--         editing[row.$$index + '-name'] = true-->

         </ng-template>

         <ng-template #dateTmpl let-value="value" let-row="row" let-i="index">
           <time>{{value | dateFormat }}</time>
         </ng-template>

          <ng-template #boolTmpl let-value="value" let-row="row" let-column="column" let-i="index">
              <img class="validStatus-img" src="./assets/images/{{value?column.trueImg:column.falseImg}}"  />
              <span>{{value ? "Valid" : "Invalid"}}</span>
          </ng-template>

      </ngx-datatable>

-- Angular 4 -- ngx datatable 8.0.0

arlowhite commented 7 years ago

I've encountered this bug. I thought it was related to Angular change detection, because when I focused a md-input-container, suddenly ngx-datatable displayed. I tried manually calling markForChanges() in a timeout, but it didn't work. I think it may be related to this visibility directive thing: https://github.com/swimlane/ngx-datatable/blob/908f1216818efe9c8c6322243070eed3cd7479ea/src/directives/visibility.directive.ts

I had another scrolling issue, which I fixed by manually scrolling datatableBody, but I think it could work as a hack to fix this issue.

Setup

  @ViewChild(DatatableComponent)
  dataTable: DatatableComponent;

  private datatableBodyElement: Element;

  ngAfterViewInit(): void {
    this.datatableBodyElement = this.dataTable.element.querySelector('datatable-body');
  }

Scroll this.datatableBodyElement.scrollTop = offsetY;

So maybe if you scrolled 1px back and forth it would display the table?

professorjoshua commented 7 years ago

@arlowhite Thank you for the suggestion, however, it didn't work for me. I tried @ipassynk proposed solution too but I'm still stuck.

arlowhite commented 7 years ago

@professorjoshua did you scroll after a timeout (setTimeout)? maybe scroll back and forth 1px?

If all else fails, maybe force change detection first with markForCheck(), then after 1ms timeout scroll 1px.

If none of that works, it must be related to ngx-datatable's VisibilityDirective, but I don't know much about it.

rodrigoEclipsa commented 7 years ago

The same thing happens to me, I want to use the same table for multiple data and columns... image

adammedford commented 7 years ago

I believe this is caused by the same bug as #843. Basically, the rows are rendered in the latter scenario but they retain the scroll offset from before so they are rendered below the container. Scrolling then recalculates those rows positions so they appear in the viewport.

rodrigoEclipsa commented 7 years ago

I have had to abandon the use of ngx-datatable, given the many problems and bugs I have encountered. I think that the vitualScroll would have to rethink, since in HTML the problem is not the displacement, if not the deferred creation of the rows. You can move a table element with more than 10000 rows and it will be faster than the virtualScroll with 100 rows

professorjoshua commented 7 years ago

I have also had to abandon the use of the ngx-datatable. Sorry.

brokenhands commented 7 years ago

After a lot of attempted work-arounds, I've found something that worked for my project.

For me this bug was manifesting when the user had scrolled beyond the viewport, and we changed the rows array (shifting categories in my case). The new recordset would always appear blank if it was short, or it would be invisible until scrolling for longer recordsets. The code previously reset the rows to being empty while waiting on the service like this: this.table.rows = []; In my case, adding a simple object with a single column property in it was enough: this.table.rows = [{name: ""}];

jordanburke commented 7 years ago

We ran into the same thing as @brokenhands clearing the results before the next set, and confirming the workaround also works! The only downside is if you have the row count display shown it does count it as a single row in the interim.

markanthonyg commented 6 years ago

Finally found a hack for this:

http.get(...).subscribe(res => {
     /// set rows
     setTimeout(function() { $('datatable-body').scrollTop(1); }, 1);
     setTimeout(function() { $('datatable-body').scrollTop(0); }, 1);
}
rodrigoEclipsa commented 6 years ago

a solution with a timer .. better step ....

HackPoint commented 6 years ago

@rodrigoEclipsa could you please demonstrate? Could you show how have you solved it please

LeonardoWiest commented 4 years ago

It's working for me now, after adding this source code:

setTimeout(() => { this.myGrid.element.getElementsByTagName('datatable-body')[0].scrollTop++; }, 1);

crosskpixel commented 3 years ago

I solved this this way, following the idea of @markanthonyg , but I did this implementation using directives and listening to the input of rows , that when it is empty there will be no horizontal scroll so I save the last scroll and when there is data again I apply this routine, but I decided to hide with 'opacity' the element because in the meantime he made the screen dance dance , and this was a problem for me

Directive.

import { Directive, ElementRef, HostListener, Input, OnChanges } from '@angular/core';

@Directive({
  selector: '[tableScroll]',
})
export class TableScrollDirective implements OnChanges {

  private lastScrollLeft: number = 0;
  @Input() rows = [];

  constructor(private eleRef: ElementRef) {}

  async ngOnChanges() {
    let tableBody = this.eleRef.nativeElement.children.item(0).querySelector(".datatable-body");
    if(this.rows.length === 0) {
        this.lastScrollLeft = tableBody?.scrollLeft || 0;
    } else {
        tableBody.style.opacity = 0;
        tableBody.scroll({
            left: (this.lastScrollLeft - 1) || 0
        });
        await new Promise(resolve => setTimeout(resolve));
        tableBody.scroll({
            left: (this.lastScrollLeft + 1) || 0
        })
        tableBody.style.opacity = 1;
    }
  }

}

HTML


<ngx-datatable
      tableScroll   <-------------------------------------- **directive**
      [scrollbarV]="true"
      [scrollbarH]="true"
      [messages]="{
        emptyMessage: loading
          ? 'loading...'
          : 'no documents !'
      }"
      class="ngx-border material expandable"
      [ngClass]="{
        'ngx-table-height-item': alreadyHadRow,
        'ngx-table-height-item-small': !alreadyHadRow
      }"
      [count]="page.totalElements"
      [externalPaging]="false"
      [rows]="rows"
      columnMode="standard"
      [headerHeight]="50"
      [footerHeight]="50"
      [rowHeight]="32"
      [limit]="page.size"
      [selected]="selected"
      (activate)="onActivate($event)"
      (select)="onSelect($event)"
      (sort)="onSort($event)"
      [externalSorting]="true"
    >
....
</ngx-datatable>
shuZro commented 3 years ago

A way I solved this issue was as so:

this.rows.push(undefined); setTimeout(() => { this.rows = this.rows.filter((row) => row); }, 1);

Basically will force a re-render of the table. Will also work if a given list becomes smaller which results in a blank screen.

elcsiga commented 2 years ago

It seems that if we remove some rows and scroll bar disappears, the data table element does not reset the scrollPos values itself. So I ended up adding something like this in my datatable component which uses ngx-data-table:

@Input() set data(value: any[]) {
    this.rows = value;

    setTimeout(() => {
        // here we reset scrollPos values on each data change if there is no scroller present.
        if (!this.dataTable.bodyComponent.scroller) {
            this.dataTable.bodyComponent.onBodyScroll({
                scrollYPos: 0,
                scrollXPos: 0
            });
        }
    }, 0);
}