Skip to content

Commit b93361d

Browse files
MayaKirovaMKirova
andauthored
Implement resize observer to auto-reset scroll position when scrollbar element is detached/attached to DOM. (#8505)
* feat(igxForOf): Restore scroll position when scrollbar is detached from the DOM and then attached again as a result of projected containers. Co-authored-by: MKirova <MKirova@DEV-MKIROVA>
1 parent 72d1839 commit b93361d

File tree

6 files changed

+673
-645
lines changed

6 files changed

+673
-645
lines changed

projects/igniteui-angular/src/lib/directives/for-of/base.helper.component.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@ import {
66
ChangeDetectorRef,
77
OnDestroy,
88
Directive,
9-
AfterViewInit
9+
AfterViewInit,
10+
Inject,
11+
NgZone
1012
} from '@angular/core';
13+
import { DOCUMENT } from '@angular/common';
14+
import { Subject } from 'rxjs';
15+
import { takeUntil, throttleTime } from 'rxjs/operators';
16+
import { resizeObservable, isIE } from '../../core/utils';
1117

1218
@Directive({
1319
selector: '[igxVirtualHelperBase]'
@@ -21,16 +27,25 @@ export class VirtualHelperBaseDirective implements OnDestroy, AfterViewInit {
2127

2228
private _afterViewInit = false;
2329
private _scrollNativeSize: number;
30+
private _detached = false;
31+
protected destroy$ = new Subject<any>();
32+
2433

2534
ngAfterViewInit() {
2635
this._afterViewInit = true;
36+
const delayTime = isIE() ? 40 : 0;
37+
this._zone.runOutsideAngular(() => {
38+
resizeObservable(this.nativeElement).pipe(
39+
throttleTime(delayTime),
40+
takeUntil(this.destroy$)).subscribe((event) => this.handleMutations(event));
41+
});
2742
}
2843

2944
@HostListener('scroll', ['$event'])
3045
onScroll(event) {
3146
this.scrollAmount = event.target.scrollTop || event.target.scrollLeft;
3247
}
33-
constructor(public elementRef: ElementRef, public cdr: ChangeDetectorRef) {
48+
constructor(public elementRef: ElementRef, public cdr: ChangeDetectorRef, protected _zone: NgZone, @Inject(DOCUMENT) public document) {
3449
this._scrollNativeSize = this.calculateScrollNativeSize();
3550
}
3651

@@ -40,6 +55,8 @@ export class VirtualHelperBaseDirective implements OnDestroy, AfterViewInit {
4055

4156
public ngOnDestroy() {
4257
this.destroyed = true;
58+
this.destroy$.next(true);
59+
this.destroy$.complete();
4360
}
4461

4562
public set size(value) {
@@ -60,6 +77,23 @@ export class VirtualHelperBaseDirective implements OnDestroy, AfterViewInit {
6077
return this._scrollNativeSize;
6178
}
6279

80+
protected get isAttachedToDom(): boolean {
81+
return this.document.body.contains(this.nativeElement);
82+
}
83+
84+
protected handleMutations(event) {
85+
const hasSize = !(event[0].contentRect.height === 0 && event[0].contentRect.width === 0);
86+
if (!hasSize && !this.isAttachedToDom) {
87+
// scroll bar detached from DOM
88+
this._detached = true;
89+
} else if (this._detached && hasSize && this.isAttachedToDom) {
90+
// attached back now.
91+
this.restoreScroll();
92+
}
93+
}
94+
95+
protected restoreScroll() {}
96+
6397
public calculateScrollNativeSize() {
6498
const div = document.createElement('div');
6599
const style = div.style;

projects/igniteui-angular/src/lib/directives/for-of/horizontal.virtual.helper.component.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { Component, ElementRef, HostBinding, Input, ViewChild, ViewContainerRef, ChangeDetectorRef } from '@angular/core';
1+
import { Component, ElementRef, HostBinding, Input, ViewChild, ViewContainerRef, ChangeDetectorRef, Inject, NgZone } from '@angular/core';
22
import { VirtualHelperBaseDirective } from './base.helper.component';
3+
import { DOCUMENT } from '@angular/common';
34

45
/**
56
* @hidden
@@ -14,7 +15,11 @@ export class HVirtualHelperComponent extends VirtualHelperBaseDirective {
1415
@HostBinding('class')
1516
public cssClasses = 'igx-vhelper--horizontal';
1617

17-
constructor(public elementRef: ElementRef, public cdr: ChangeDetectorRef) {
18-
super(elementRef, cdr);
19-
}
18+
constructor(public elementRef: ElementRef, public cdr: ChangeDetectorRef, protected _zone: NgZone, @Inject(DOCUMENT) public document) {
19+
super(elementRef, cdr, _zone, document);
20+
}
21+
22+
protected restoreScroll() {
23+
this.nativeElement.scrollLeft = this.scrollAmount;
24+
}
2025
}

projects/igniteui-angular/src/lib/directives/for-of/virtual.helper.component.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Component, ElementRef, HostBinding, Input, ViewChild, ViewContainerRef,
2-
ChangeDetectorRef, OnDestroy, OnInit } from '@angular/core';
2+
ChangeDetectorRef, OnDestroy, OnInit, Inject, NgZone } from '@angular/core';
33
import { VirtualHelperBaseDirective } from './base.helper.component';
4+
import { DOCUMENT } from '@angular/common';
45

56
@Component({
67
selector: 'igx-virtual-helper',
@@ -20,11 +21,15 @@ export class VirtualHelperComponent extends VirtualHelperBaseDirective implement
2021
@HostBinding('class')
2122
public cssClasses = 'igx-vhelper--vertical';
2223

23-
constructor(public elementRef: ElementRef, public cdr: ChangeDetectorRef) {
24-
super(elementRef, cdr);
24+
constructor(public elementRef: ElementRef, public cdr: ChangeDetectorRef, protected _zone: NgZone, @Inject(DOCUMENT) public document) {
25+
super(elementRef, cdr, _zone, document);
2526
}
2627

2728
ngOnInit() {
2829
this.scrollWidth = this.scrollNativeSize;
2930
}
31+
32+
protected restoreScroll() {
33+
this.nativeElement.scrollTop = this.scrollAmount;
34+
}
3035
}

projects/igniteui-angular/src/lib/grids/grid/grid.component.spec.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1958,6 +1958,39 @@ describe('IgxGrid Component Tests #grid', () => {
19581958
expect(parseInt(window.getComputedStyle(gridBody.nativeElement).height, 10)).toBe(expectedHeight);
19591959
expect(parseInt(window.getComputedStyle(grid.nativeElement).height, 10)).toBe(300);
19601960
});
1961+
1962+
it('IgxTabs: should persist scroll position after changing tabs.', async () => {
1963+
const grid = fix.componentInstance.grid2;
1964+
fix.detectChanges();
1965+
const tab = fix.componentInstance.tabs;
1966+
1967+
tab.tabs.toArray()[1].select();
1968+
await wait(100);
1969+
fix.detectChanges();
1970+
1971+
grid.navigateTo(grid.data.length - 1, grid.columns.length - 1);
1972+
await wait(100);
1973+
fix.detectChanges();
1974+
1975+
const scrTop = grid.verticalScrollContainer.getScroll().scrollTop;
1976+
const scrLeft = grid.dataRowList.first.virtDirRow.getScroll().scrollLeft;
1977+
1978+
expect(scrTop).not.toBe(0);
1979+
expect(scrLeft).not.toBe(0);
1980+
1981+
tab.tabs.toArray()[0].select();
1982+
await wait(100);
1983+
fix.detectChanges();
1984+
1985+
tab.tabs.toArray()[1].select();
1986+
await wait(100);
1987+
fix.detectChanges();
1988+
await wait(100);
1989+
1990+
// check scrollTop/scrollLeft was persisted.
1991+
expect(grid.verticalScrollContainer.getScroll().scrollTop).toBe(scrTop);
1992+
expect(grid.dataRowList.first.virtDirRow.getScroll().scrollLeft).toBe(scrLeft);
1993+
});
19611994
});
19621995

19631996
describe('IgxGrid - footer section', () => {

0 commit comments

Comments
 (0)