From 60ec9e097047c7cec9f961e21eb55c0528636944 Mon Sep 17 00:00:00 2001
From: Austin <amcdaniel2@gmail.com>
Date: Thu, 16 Nov 2017 20:30:33 -0600
Subject: [PATCH] feat(perf): attempt at making better performance for offsetx

---
 src/components/body/body-row.component.ts | 22 +++++++++++++--
 src/components/body/body.component.ts     | 34 ++++-------------------
 src/components/body/scroller.component.ts | 26 ++++++++++++-----
 src/components/body/scroller.service.ts   |  7 +++++
 src/components/datatable.component.ts     |  4 ++-
 src/components/header/header.component.ts | 34 ++++++++++++++++++-----
 6 files changed, 81 insertions(+), 46 deletions(-)
 create mode 100644 src/components/body/scroller.service.ts

diff --git a/src/components/body/body-row.component.ts b/src/components/body/body-row.component.ts
index 19e7f7c56..8b6765c1f 100644
--- a/src/components/body/body-row.component.ts
+++ b/src/components/body/body-row.component.ts
@@ -8,6 +8,8 @@ import {
 } from '../../utils';
 import { ScrollbarHelper } from '../../services';
 import { MouseEvent, KeyboardEvent } from '../../events';
+import { ScrollerService } from './scroller.service';
+import { Subscription } from 'rxjs/Subscription';
 
 @Component({
   selector: 'datatable-body-row',
@@ -16,7 +18,7 @@ import { MouseEvent, KeyboardEvent } from '../../events';
     <div
       *ngFor="let colGroup of columnsByPin; let i = index; trackBy: trackByGroups"
       class="datatable-row-{{colGroup.type}} datatable-row-group"
-      [ngStyle]="stylesByGroup(colGroup.type)">
+      [ngStyle]="groupStyles[colGroup.type]">
       <datatable-body-cell
         *ngFor="let column of colGroup.columns; let ii = index; trackBy: columnTrackingFn"
         tabindex="-1"
@@ -105,15 +107,30 @@ export class DataTableBodyRowComponent implements DoCheck {
   _columns: any[];
   _innerWidth: number;
 
+  groupStyles = {
+    left: {},
+    center: {},
+    right: {}
+  };
+
   private rowDiffer: KeyValueDiffer<{}, {}>;
+  private subscription: Subscription;
 
   constructor(
+      private scroller: ScrollerService,
       private differs: KeyValueDiffers,
       private scrollbarHelper: ScrollbarHelper,
       private cd: ChangeDetectorRef, 
       element: ElementRef) {
     this.element = element.nativeElement;
     this.rowDiffer = differs.find({}).create();
+
+    this.subscription = scroller.offset.subscribe((offset: any) => {
+      this.groupStyles.left = this.stylesByGroup('left', offset.scrollXPos);
+      this.groupStyles.center = this.stylesByGroup('center', offset.scrollXPos;
+      this.groupStyles.right = this.stylesByGroup('right', offset.scrollXPos);
+      this.cd.markForCheck();
+    });
   }
 
   ngDoCheck(): void {
@@ -130,9 +147,8 @@ export class DataTableBodyRowComponent implements DoCheck {
     return column.$$id;
   }
 
-  stylesByGroup(group: string) {
+  stylesByGroup(group: string, offsetX: number) {
     const widths = this.columnGroupWidths;
-    const offsetX = this.offsetX;
 
     const styles = {
       width: `${widths[group]}px`
diff --git a/src/components/body/body.component.ts b/src/components/body/body.component.ts
index 26a72206c..bbc9123a2 100644
--- a/src/components/body/body.component.ts
+++ b/src/components/body/body.component.ts
@@ -321,12 +321,14 @@ export class DataTableBodyComponent implements OnInit, OnDestroy {
       });
     }
 
-    this.offsetY = scrollYPos;
     this.offsetX = scrollXPos;
 
-    this.updateIndexes();
-    this.updatePage(event.direction);
-    this.updateRows();
+    if (this.offsetY !== scrollYPos) {
+      this.offsetY = scrollYPos;
+      this.updateIndexes();
+      this.updatePage(event.direction);
+      this.updateRows();
+    }
   }
 
   /**
@@ -645,30 +647,6 @@ export class DataTableBodyComponent implements OnInit, OnDestroy {
   columnTrackingFn(index: number, column: any): any {
     return column.$$id;
   }
-
-  /**
-   * Gets the row pinning group styles
-   */
-  stylesByGroup(group: string) {
-    const widths = this.columnGroupWidths;
-    const offsetX = this.offsetX;
-
-    const styles = {
-      width: `${widths[group]}px`
-    };
-
-    if(group === 'left') {
-      translateXY(styles, offsetX, 0);
-    } else if(group === 'right') {
-      const bodyWidth = parseInt(this.innerWidth + '', 0);
-      const totalDiff = widths.total - bodyWidth;
-      const offsetDiff = totalDiff - offsetX;
-      const offset = offsetDiff * -1;
-      translateXY(styles, offset, 0);
-    }
-
-    return styles;
-  }
   
   /**
    * Returns if the row was expanded and set default row expansion when row expansion is empty
diff --git a/src/components/body/scroller.component.ts b/src/components/body/scroller.component.ts
index ea4eb06e4..0e8dc9eeb 100644
--- a/src/components/body/scroller.component.ts
+++ b/src/components/body/scroller.component.ts
@@ -1,9 +1,10 @@
 import {
-  Component, Input, ElementRef, Output, EventEmitter,
+  Component, Input, ElementRef, Output, EventEmitter, NgZone,
   OnInit, OnDestroy, HostBinding, ChangeDetectionStrategy
 } from '@angular/core';
 
 import { MouseEvent } from '../../events';
+import { ScrollerService } from './scroller.service';
 
 @Component({
   selector: 'datatable-scroller',
@@ -36,7 +37,10 @@ export class ScrollerComponent implements OnInit, OnDestroy {
   parentElement: any;
   onScrollListener: any;
 
-  constructor(element: ElementRef) {
+  constructor(
+      private ngZone: NgZone, 
+      private scroller: ScrollerService, 
+      element: ElementRef) {
     this.element = element.nativeElement;
   }
 
@@ -44,7 +48,9 @@ export class ScrollerComponent implements OnInit, OnDestroy {
     // manual bind so we don't always listen
     if (this.scrollbarV || this.scrollbarH) {
       this.parentElement = this.element.parentElement.parentElement;
-      this.parentElement.addEventListener('scroll', this.onScrolled.bind(this));
+      this.ngZone.runOutsideAngular(() => {
+        this.parentElement.addEventListener('scroll', this.onScrolled.bind(this));
+      });
     }
   }
 
@@ -62,10 +68,11 @@ export class ScrollerComponent implements OnInit, OnDestroy {
 
   onScrolled(event: MouseEvent): void {
     const dom: Element = <Element>event.currentTarget;
-    this.scrollYPos = dom.scrollTop;
-    this.scrollXPos = dom.scrollLeft;
-
-    requestAnimationFrame(this.updateOffset.bind(this));
+    requestAnimationFrame(() => {
+      this.scrollYPos = dom.scrollTop;
+      this.scrollXPos = dom.scrollLeft;
+      this.updateOffset();
+    });
   }
 
   updateOffset(): void {
@@ -82,6 +89,11 @@ export class ScrollerComponent implements OnInit, OnDestroy {
       scrollXPos: this.scrollXPos
     });
 
+    this.scroller.offset.next({
+      scrollYPos: this.scrollYPos,
+      scrollXPos: this.scrollXPos
+    });
+
     this.prevScrollYPos = this.scrollYPos;
     this.prevScrollXPos = this.scrollXPos;
   }
diff --git a/src/components/body/scroller.service.ts b/src/components/body/scroller.service.ts
new file mode 100644
index 000000000..93f7c32ba
--- /dev/null
+++ b/src/components/body/scroller.service.ts
@@ -0,0 +1,7 @@
+import { Injectable } from '@angular/core';
+import { Subject } from 'rxjs/Subject';
+
+@Injectable()
+export class ScrollerService {
+  offset = new Subject();
+}
diff --git a/src/components/datatable.component.ts b/src/components/datatable.component.ts
index 06a015c4f..b410a9f36 100644
--- a/src/components/datatable.component.ts
+++ b/src/components/datatable.component.ts
@@ -18,6 +18,7 @@ import { DataTableColumnDirective } from './columns';
 import { DatatableRowDetailDirective } from './row-detail';
 import { DatatableFooterDirective } from './footer';
 import { MouseEvent } from '../events';
+import { ScrollerService } from './body/scroller.service';
 
 @Component({
   selector: 'ngx-datatable',
@@ -31,7 +32,6 @@ import { MouseEvent } from '../events';
         [sortType]="sortType"
         [scrollbarH]="scrollbarH"
         [innerWidth]="innerWidth"
-        [offsetX]="offsetX"
         [dealsWithGroup]="groupedRows"
         [columns]="_internalColumns"
         [headerHeight]="headerHeight"
@@ -100,6 +100,7 @@ import { MouseEvent } from '../events';
   changeDetection: ChangeDetectionStrategy.OnPush,
   encapsulation: ViewEncapsulation.None,
   styleUrls: ['./datatable.component.scss'],
+  providers: [ScrollerService],
   host: {
     class: 'ngx-datatable'
   }
@@ -620,6 +621,7 @@ export class DatatableComponent implements OnInit, DoCheck, AfterViewInit {
   _columnTemplates: QueryList<DataTableColumnDirective>;
 
   constructor(
+    private scroller: ScrollerService,
     private scrollbarHelper: ScrollbarHelper,
     private cd: ChangeDetectorRef,
     element: ElementRef,
diff --git a/src/components/header/header.component.ts b/src/components/header/header.component.ts
index fb9e57b3a..8be171598 100644
--- a/src/components/header/header.component.ts
+++ b/src/components/header/header.component.ts
@@ -1,10 +1,12 @@
 import {
-  Component, Output, EventEmitter, Input, HostBinding
+  Component, Output, EventEmitter, Input, HostBinding, ChangeDetectorRef, OnDestroy
 } from '@angular/core';
 import { SortType, SelectionType } from '../../types';
 import { columnsByPin, columnGroupWidths, columnsByPinArr, translateXY } from '../../utils';
 import { DataTableColumnDirective } from '../columns';
 import { MouseEvent } from '../../events';
+import { ScrollerService } from '../body/scroller.service';
+import { Subscription } from 'rxjs/Subscription';
 
 @Component({
   selector: 'datatable-header',
@@ -14,11 +16,10 @@ import { MouseEvent } from '../../events';
       (reorder)="onColumnReordered($event)"
       [style.width.px]="columnGroupWidths.total"
       class="datatable-header-inner">
-     
       <div
         *ngFor="let colGroup of columnsByPin; trackBy: trackByGroups"
         [class]="'datatable-row-' + colGroup.type"
-        [ngStyle]="stylesByGroup(colGroup.type)">
+        [ngStyle]="groupStyles[colGroup.type]">
         <datatable-header-cell
           *ngFor="let column of colGroup.columns; trackBy: columnTrackingFn"
           resizeable
@@ -53,7 +54,7 @@ import { MouseEvent } from '../../events';
     class: 'datatable-header'
   }
 })
-export class DataTableHeaderComponent {
+export class DataTableHeaderComponent implements OnDestroy {
   @Input() sortAscendingIcon: any;
   @Input() sortDescendingIcon: any;
   @Input() scrollbarH: boolean;
@@ -74,7 +75,6 @@ export class DataTableHeaderComponent {
     return this._innerWidth;
   }
 
-  @Input() offsetX: number;
   @Input() sorts: any[];
   @Input() sortType: SortType;
   @Input() allRowsSelected: boolean;
@@ -119,6 +119,27 @@ export class DataTableHeaderComponent {
   _columns: any[];
   _headerHeight: string;
 
+  groupStyles = {
+    left: {},
+    center: {},
+    right: {}
+  };
+
+  private subscription: Subscription;
+
+  constructor(private scroller: ScrollerService, private cd: ChangeDetectorRef) {
+    this.subscription = scroller.offset.subscribe((offset: any) => {
+      this.groupStyles.left = this.stylesByGroup('left', offset.scrollXPos);
+      this.groupStyles.center = this.stylesByGroup('center', offset.scrollXPos;
+      this.groupStyles.right = this.stylesByGroup('right', offset.scrollXPos);
+      this.cd.detectChanges();
+    });
+  }
+
+  ngOnDestroy() {
+    this.subscription.unsubscribe();
+  }
+
   onLongPressStart({ event, model }: { event: any, model: any }) {
     model.dragging = true;
     this.dragEventTarget = event;
@@ -214,9 +235,8 @@ export class DataTableHeaderComponent {
     return sorts;
   }
 
-  stylesByGroup(group: string): any {
+  stylesByGroup(group: string, offsetX: number): any {
     const widths = this.columnGroupWidths;
-    const offsetX = this.offsetX;
 
     const styles = {
       width: `${widths[group]}px`