ngx-datatable
ngx-datatable copied to clipboard
Horizontal scroll bar is not shown when datatable is empty
I'm submitting a ... (check one with "x")
[x] bug report => search github for a similar issue or PR before submitting
[ ] feature request
[ ] support request => Please do not submit support request here, post on Stackoverflow or Gitter
Current behavior
If I am using [scrollbarH]="true" and if there it is no rows in datatable, the horizontal scroll bar is not shown, and header is cut off and cannot see more header columns in right side.
Expected behavior
Horizontal scroll bar shows on page and user should be able to see header columns even datatable is empty.
Reproduction of the problem
in demo HorzVertScrolling, comment out this code snippet: /* this.fetch((data) => { this.rows = data; }); */
you can find there is no horizontal scrolling bar and state column on right side is hidden.

What is the motivation / use case for changing the behavior?
This is not a user friendly experience when the table is empty.
Please tell us about your environment:
- Table version: 0.8.x
swimlane/ngx-datatable: "11.2.0",
- Angular version: 2.0.x
angular/common: "^4.1.3"
- Browser: [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ]
Chrome
- Language: [all | TypeScript X.X | ES6/7 | ES5] typescript: "2.5.3",
I have the same issue
I had same issue, but doing lots of R&D , I did some custom code, it solve my problem. I created a common method and call from component when data not available.
like : setEmptyMessage() { let msg = 'No data to display.'; if (!this.tableData.length) { this.tableData = [{ 'message': msg }]; } else { delete this.tableData[0]['message']; msg = ''; } this.setEmptyMessageDataTable(msg); }
// create a common method like this setEmptyMessageDataTable(msg) { const hideClass = document.querySelector('datatable-body-row'); const divelm = document.querySelector('#noMsg'); if (msg) { if (hideClass) { hideClass.setAttribute('style', 'display:none;'); }
if (divelm) {
document.querySelector('#noMsg').remove();
}
// Create a new element
const element = document.createElement('p');
element.id = 'noMsg';
element.innerHTML = msg;
document.querySelector('datatable-row-wrapper').appendChild(element);
} else {
if (divelm) {
document.querySelector('#noMsg').remove();
}
if (hideClass) {
hideClass.removeAttribute('style');
}
}
}
// use setEmptyMessage() where you are fetching dataTable data
this.yourApi.method(req).subscribe( results => { this.setEmptyMessage(); }, error => { } )
I am facing the same issue, Has anyone found the solution?
I had same issue, but doing lots of R&D , I did some custom code, it solve my problem. I created a common method and call from component when data not available.
like : setEmptyMessage() { let msg = 'No data to display.'; if (!this.tableData.length) { this.tableData = [{ 'message': msg }]; } else { delete this.tableData[0]['message']; msg = ''; } this.setEmptyMessageDataTable(msg); }
// create a common method like this setEmptyMessageDataTable(msg) { const hideClass = document.querySelector('datatable-body-row'); const divelm = document.querySelector('#noMsg'); if (msg) { if (hideClass) { hideClass.setAttribute('style', 'display:none;'); }
if (divelm) { document.querySelector('#noMsg').remove(); } // Create a new element const element = document.createElement('p'); element.id = 'noMsg'; element.innerHTML = msg; document.querySelector('datatable-row-wrapper').appendChild(element); } else { if (divelm) { document.querySelector('#noMsg').remove(); } if (hideClass) { hideClass.removeAttribute('style'); } } }
// use setEmptyMessage() where you are fetching dataTable data
this.yourApi.method(req).subscribe( results => { this.setEmptyMessage(); }, error => { } )
Do you have any working example. I tried the same code but did not work.
import { Directive, ElementRef, Renderer2, OnInit, AfterViewChecked } from '@angular/core';
@Directive({
selector: '[emptyRow]'
})
export class EmptyRowDirective implements OnInit, AfterViewChecked {
textElement: any;
actionElement : any;
constructor(private renderer: Renderer2,
private hostElement: ElementRef) {
}
ngOnInit() {
this.hostElement.nativeElement.getElementsByClassName('datatable-body')[0].addEventListener("scroll", event => {
this.hostElement.nativeElement.getElementsByClassName('datatable-header')[0].scrollLeft = event.srcElement.scrollLeft;
});
}
ngAfterViewChecked(){
this.hostElement.nativeElement.getElementsByClassName('empty-row')[0].style.width = this.hostElement.nativeElement.getElementsByClassName('datatable-row-center')[0].style.width;
}
}
Created a directive to achieve this. Add this directive to ngx-datable tag. This is working for now. Any suggestions for improvements is welcome.
import { Directive, ElementRef, Renderer2, OnInit, AfterViewChecked } from '@angular/core'; @Directive({ selector: '[emptyRow]' }) export class EmptyRowDirective implements OnInit, AfterViewChecked { textElement: any; actionElement : any; constructor(private renderer: Renderer2, private hostElement: ElementRef) { } ngOnInit() { this.hostElement.nativeElement.getElementsByClassName('datatable-body')[0].addEventListener("scroll", event => { this.hostElement.nativeElement.getElementsByClassName('datatable-header')[0].scrollLeft = event.srcElement.scrollLeft; }); } ngAfterViewChecked(){ this.hostElement.nativeElement.getElementsByClassName('empty-row')[0].style.width = this.hostElement.nativeElement.getElementsByClassName('datatable-row-center')[0].style.width; } }
Created a directive to achieve this. Add this directive to ngx-datable tag. This is working for now. Any suggestions for improvements is welcome.
This should be a slight improvement over your previous solution:
import { Directive, Renderer2, ElementRef, AfterViewInit, AfterViewChecked } from "@angular/core";
/**
* Directive to add an empty row to `ngx-datatable components that do not have any data.
* This fixes a bug with ngx-datatable in which empty tables do not have a scrollable header.
*/
@Directive({
selector: "[datNgxEmptyRow]"
})
export class NgxEmptyRowDirective implements AfterViewInit, AfterViewChecked {
private datatableBodyEl: HTMLElement;
private datatableHeaderEl: HTMLElement;
private datatableEmptyRowEl: HTMLElement;
private datatableRowCenterEl: HTMLElement;
constructor(private renderer: Renderer2, private hostElRef: ElementRef) {}
public ngAfterViewInit() {
this.setUpElementReferences();
if (this.shouldApplyHack) {
this.hackEmptyCellWidth();
this.setUpHeaderScrollListener();
}
}
public ngAfterViewChecked() {
if (this.shouldApplyHack) {
this.hackEmptyCellWidth();
}
}
private setUpHeaderScrollListener() {
this.renderer.listen(this.datatableBodyEl, "scroll", (event: any) => {
this.datatableHeaderEl.scrollLeft = event.srcElement.scrollLeft;
});
}
private hackEmptyCellWidth() {
const newWidth = this.datatableRowCenterEl.style.width;
this.renderer.setStyle(this.datatableEmptyRowEl, "width", newWidth);
}
private setUpElementReferences() {
const hostEl = this.hostElRef.nativeElement;
this.datatableBodyEl = hostEl.getElementsByClassName("datatable-body")[0];
this.datatableHeaderEl = hostEl.getElementsByClassName("datatable-header")[0];
this.datatableEmptyRowEl = hostEl.getElementsByClassName("empty-row")[0];
this.datatableRowCenterEl = hostEl.getElementsByClassName("datatable-row-center")[0];
}
private get shouldApplyHack(): boolean {
return !!this.datatableEmptyRowEl;
}
}
It's not necessary to be this verbose, I just prefer it for my specific use case. This adds a quick check to avoid errors and uses the Render2
class
import { Directive, ElementRef, Renderer2, OnInit, AfterViewChecked } from '@angular/core'; @Directive({ selector: '[emptyRow]' }) export class EmptyRowDirective implements OnInit, AfterViewChecked { textElement: any; actionElement : any; constructor(private renderer: Renderer2, private hostElement: ElementRef) { } ngOnInit() { this.hostElement.nativeElement.getElementsByClassName('datatable-body')[0].addEventListener("scroll", event => { this.hostElement.nativeElement.getElementsByClassName('datatable-header')[0].scrollLeft = event.srcElement.scrollLeft; }); } ngAfterViewChecked(){ this.hostElement.nativeElement.getElementsByClassName('empty-row')[0].style.width = this.hostElement.nativeElement.getElementsByClassName('datatable-row-center')[0].style.width; } }
Created a directive to achieve this. Add this directive to ngx-datable tag. This is working for now. Any suggestions for improvements is welcome.
This should be a slight improvement over your previous solution:
import { Directive, Renderer2, ElementRef, AfterViewInit, AfterViewChecked } from "@angular/core"; /** * Directive to add an empty row to `ngx-datatable components that do not have any data. * This fixes a bug with ngx-datatable in which empty tables do not have a scrollable header. */ @Directive({ selector: "[datNgxEmptyRow]" }) export class NgxEmptyRowDirective implements AfterViewInit, AfterViewChecked { private datatableBodyEl: HTMLElement; private datatableHeaderEl: HTMLElement; private datatableEmptyRowEl: HTMLElement; private datatableRowCenterEl: HTMLElement; constructor(private renderer: Renderer2, private hostElRef: ElementRef) {} public ngAfterViewInit() { this.setUpElementReferences(); if (this.shouldApplyHack) { this.hackEmptyCellWidth(); this.setUpHeaderScrollListener(); } } public ngAfterViewChecked() { if (this.shouldApplyHack) { this.hackEmptyCellWidth(); } } private setUpHeaderScrollListener() { this.renderer.listen(this.datatableBodyEl, "scroll", (event: any) => { this.datatableHeaderEl.scrollLeft = event.srcElement.scrollLeft; }); } private hackEmptyCellWidth() { const newWidth = this.datatableRowCenterEl.style.width; this.renderer.setStyle(this.datatableEmptyRowEl, "width", newWidth); } private setUpElementReferences() { const hostEl = this.hostElRef.nativeElement; this.datatableBodyEl = hostEl.getElementsByClassName("datatable-body")[0]; this.datatableHeaderEl = hostEl.getElementsByClassName("datatable-header")[0]; this.datatableEmptyRowEl = hostEl.getElementsByClassName("empty-row")[0]; this.datatableRowCenterEl = hostEl.getElementsByClassName("datatable-row-center")[0]; } private get shouldApplyHack(): boolean { return !!this.datatableEmptyRowEl; } }
It's not necessary to be this verbose, I just prefer it for my specific use case. This adds a quick check to avoid errors and uses the
Render2
class
I tried this fix but it "sort of work", if there is data in the data-table and I move the horizontal scrollbar to the right, the column headers are pushed to the left instead of staying in position.
Now if not working properly if we have data. Ngx datatable header and body is not aligned.
is there any solution to this problem I tried the proposed directive but not working with the pinning section
// https://github.com/swimlane/ngx-datatable/issues/1367#issuecomment-511893170
import { Directive, Renderer2, ElementRef, AfterViewInit, AfterViewChecked } from "@angular/core";
/**
* Directive to add an empty row to `ngx-datatable components that do not have any data.
* This fixes a bug with ngx-datatable in which empty tables do not have a scrollable header.
*/
@Directive({
selector: "[datNgxEmptyRow]"
})
export class NgxEmptyRowDirective implements AfterViewInit, AfterViewChecked {
private datatableBodyEl: HTMLElement;
private datatableHeaderEl: HTMLElement;
private datatableEmptyRowEl: HTMLElement;
private datatableRowCenterEl: HTMLElement;
constructor(private renderer: Renderer2, private hostElRef: ElementRef) { }
public ngAfterViewInit() {
this.setUpElementReferences();
if (this.shouldApplyHack) {
this.hackEmptyCellWidth();
this.setUpHeaderScrollListener();
}
}
public ngAfterViewChecked() {
if (this.shouldApplyHack) {
this.hackEmptyCellWidth();
}
}
private setUpHeaderScrollListener() {
this.renderer.listen(this.datatableBodyEl, "scroll", (event: any) => {
if (this.shouldApplyHack) {
this.datatableHeaderEl.scrollLeft = event.srcElement.scrollLeft;
}
});
}
private hackEmptyCellWidth() {
const newWidth = this.datatableRowCenterEl.style.width;
this.renderer.setStyle(this.datatableEmptyRowEl, "width", newWidth);
}
private setUpElementReferences() {
const hostEl = this.hostElRef.nativeElement;
this.datatableBodyEl = hostEl.getElementsByClassName("datatable-body")[0];
this.datatableHeaderEl = hostEl.getElementsByClassName("datatable-header")[0];
this.datatableEmptyRowEl = hostEl.getElementsByClassName("empty-row")[0];
this.datatableRowCenterEl = hostEl.getElementsByClassName("datatable-row-center")[0];
}
private get shouldApplyHack(): boolean {
const hostEl = this.hostElRef.nativeElement;
this.datatableEmptyRowEl = hostEl.getElementsByClassName("empty-row")[0];
return !!this.datatableEmptyRowEl;
}
}
A more improved version of the fix, instead of using scrollLeft, use the transformers that the data table uses internally, I made this improvement because we found some inconsistencies when the table has one row, the user scroll to the right and finally add another filter to remove the row, in that scenario the header scroll messed up.
// https://github.com/swimlane/ngx-datatable/issues/1367#issuecomment-511893170
import {
Directive,
Renderer2,
ElementRef,
AfterViewInit,
AfterViewChecked,
} from '@angular/core';
/**
* Directive to add an empty row to `ngx-datatable components that do not have any data.
* This fixes a bug with ngx-datatable in which empty tables do not have a scrollable header.
*/
@Directive({
selector: '[appEmptyRow]',
})
export class EmptyRowDirective implements AfterViewInit, AfterViewChecked {
private datatableBodyEl!: HTMLElement;
private datatableEmptyRowEl!: HTMLElement;
private datatableRowCenterEl!: HTMLElement;
private prevNumberOfRows!: number;
constructor(private renderer: Renderer2, private hostElRef: ElementRef) { }
public ngAfterViewInit() {
this.setUpElementReferences();
if (this.shouldApplyHack) {
this.hackEmptyCellWidth();
this.setUpHeaderScrollListener();
}
}
public ngAfterViewChecked() {
if (this.shouldApplyHack) {
this.hackEmptyCellWidth();
}
this.restartScrollingOnRowChanges();
}
private restartScrollingOnRowChanges() {
if (this.prevNumberOfRows !== this.numberOfRows) {
this.prevNumberOfRows = this.numberOfRows;
this.datatableBodyEl.scrollTo(1, 0);
this.renderer.setStyle(this.datatableRowCenterEl, 'transform', 'translate3d(0px, 0px, 0px)');
}
}
private setUpHeaderScrollListener() {
this.renderer.listen(this.datatableBodyEl, 'scroll', (event: any) => {
if (this.shouldApplyHack) {
this.renderer.setStyle(this.datatableRowCenterEl, 'transform', `translate3d(-${event.srcElement.scrollLeft}px, 0px, 0px)`)
}
});
}
private hackEmptyCellWidth() {
const newWidth = this.datatableRowCenterEl.style.width;
this.renderer.setStyle(this.datatableEmptyRowEl, 'width', newWidth);
}
private setUpElementReferences() {
const hostEl = this.hostElRef.nativeElement;
this.datatableBodyEl = hostEl.getElementsByClassName('datatable-body')[0];
this.datatableEmptyRowEl = hostEl.getElementsByClassName('empty-row')[0];
this.datatableRowCenterEl = hostEl.getElementsByClassName('datatable-row-center')[0];
}
private get shouldApplyHack(): boolean {
const hostEl = this.hostElRef.nativeElement;
this.datatableEmptyRowEl = hostEl.getElementsByClassName('empty-row')[0];
return !!this.datatableEmptyRowEl;
}
private get numberOfRows(): number {
const hostEl = this.hostElRef.nativeElement;
return hostEl.getElementsByClassName('datatable-body-row').length;
}
}
any updates on this issue?