Skip to content

Commit 5fb3b9a

Browse files
committed
refactor to use CdkScrollable
1 parent 86b338c commit 5fb3b9a

File tree

3 files changed

+70
-69
lines changed

3 files changed

+70
-69
lines changed

src/cdk/scrolling/scrollable.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,21 +48,21 @@ export class CdkScrollable implements OnInit, OnDestroy {
4848
private _destroyed = new Subject();
4949

5050
private _elementScrolled: Observable<Event> = Observable.create(observer =>
51-
this._ngZone.runOutsideAngular(() =>
52-
fromEvent(this._elementRef.nativeElement, 'scroll').pipe(takeUntil(this._destroyed))
51+
this.ngZone.runOutsideAngular(() =>
52+
fromEvent(this.elementRef.nativeElement, 'scroll').pipe(takeUntil(this._destroyed))
5353
.subscribe(observer)));
5454

55-
constructor(private _elementRef: ElementRef<HTMLElement>,
56-
private _scroll: ScrollDispatcher,
57-
private _ngZone: NgZone,
58-
@Optional() private _dir?: Directionality) {}
55+
constructor(protected elementRef: ElementRef<HTMLElement>,
56+
protected scrollDispatcher: ScrollDispatcher,
57+
protected ngZone: NgZone,
58+
@Optional() protected dir?: Directionality) {}
5959

6060
ngOnInit() {
61-
this._scroll.register(this);
61+
this.scrollDispatcher.register(this);
6262
}
6363

6464
ngOnDestroy() {
65-
this._scroll.deregister(this);
65+
this.scrollDispatcher.deregister(this);
6666
this._destroyed.next();
6767
this._destroyed.complete();
6868
}
@@ -74,7 +74,7 @@ export class CdkScrollable implements OnInit, OnDestroy {
7474

7575
/** Gets the ElementRef for the viewport. */
7676
getElementRef(): ElementRef<HTMLElement> {
77-
return this._elementRef;
77+
return this.elementRef;
7878
}
7979

8080
/**
@@ -86,8 +86,8 @@ export class CdkScrollable implements OnInit, OnDestroy {
8686
* @param options specified the offsets to scroll to.
8787
*/
8888
scrollTo(options: ExtendedScrollToOptions): void {
89-
const el = this._elementRef.nativeElement;
90-
const isRtl = this._dir && this._dir.value == 'rtl';
89+
const el = this.elementRef.nativeElement;
90+
const isRtl = this.dir && this.dir.value == 'rtl';
9191

9292
// Rewrite start & end offsets as right or left offsets.
9393
options.left = options.left == null ? (isRtl ? options.end : options.start) : options.left;
@@ -119,7 +119,7 @@ export class CdkScrollable implements OnInit, OnDestroy {
119119
}
120120

121121
private _applyScrollToOptions(options: ScrollToOptions): void {
122-
const el = this._elementRef.nativeElement;
122+
const el = this.elementRef.nativeElement;
123123

124124
if (supportsScrollBehavior()) {
125125
el.scrollTo(options);
@@ -145,7 +145,7 @@ export class CdkScrollable implements OnInit, OnDestroy {
145145
measureScrollOffset(from: 'top' | 'left' | 'right' | 'bottom' | 'start' | 'end'): number {
146146
const LEFT = 'left';
147147
const RIGHT = 'right';
148-
const el = this._elementRef.nativeElement;
148+
const el = this.elementRef.nativeElement;
149149
if (from == 'top') {
150150
return el.scrollTop;
151151
}
@@ -154,7 +154,7 @@ export class CdkScrollable implements OnInit, OnDestroy {
154154
}
155155

156156
// Rewrite start & end as left or right offsets.
157-
const isRtl = this._dir && this._dir.value == 'rtl';
157+
const isRtl = this.dir && this.dir.value == 'rtl';
158158
if (from == 'start') {
159159
from = isRtl ? RIGHT : LEFT;
160160
} else if (from == 'end') {

src/cdk/scrolling/virtual-scroll-viewport.spec.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import {ArrayDataSource} from '@angular/cdk/collections';
2-
import {CdkVirtualForOf, CdkVirtualScrollViewport, ScrollingModule} from '@angular/cdk/scrolling';
2+
import {
3+
CdkVirtualForOf,
4+
CdkVirtualScrollViewport,
5+
ScrollDispatcher,
6+
ScrollingModule
7+
} from '@angular/cdk/scrolling';
38
import {dispatchFakeEvent} from '@angular/cdk/testing';
49
import {
510
Component,
@@ -9,7 +14,7 @@ import {
914
ViewContainerRef,
1015
ViewEncapsulation
1116
} from '@angular/core';
12-
import {ComponentFixture, fakeAsync, flush, TestBed} from '@angular/core/testing';
17+
import {ComponentFixture, fakeAsync, flush, inject, TestBed} from '@angular/core/testing';
1318
import {animationFrameScheduler, Subject} from 'rxjs';
1419

1520

@@ -577,6 +582,16 @@ describe('CdkVirtualScrollViewport', () => {
577582
testComponent.maxBufferPx = 99;
578583
expect(() => finishInit(fixture)).toThrow();
579584
}));
585+
586+
it('should register and degregister with ScrollDispatcher',
587+
fakeAsync(inject([ScrollDispatcher], (dispatcher: ScrollDispatcher) => {
588+
spyOn(dispatcher, 'register').and.callThrough();
589+
spyOn(dispatcher, 'deregister').and.callThrough();
590+
finishInit(fixture);
591+
expect(dispatcher.register).toHaveBeenCalledWith(testComponent.viewport);
592+
fixture.destroy();
593+
expect(dispatcher.deregister).toHaveBeenCalledWith(testComponent.viewport);
594+
})));
580595
});
581596

582597
describe('with RTL direction', () => {
@@ -751,9 +766,9 @@ class FixedSizeVirtualScroll {
751766
@Component({
752767
template: `
753768
<cdk-virtual-scroll-viewport dir="rtl"
754-
[itemSize]="itemSize" [bufferSize]="bufferSize" [orientation]="orientation"
755-
[style.height.px]="viewportHeight" [style.width.px]="viewportWidth"
756-
(scrolledIndexChange)="scrolledToIndex = $event">
769+
[itemSize]="itemSize" [minBufferPx]="minBufferPx" [maxBufferPx]="maxBufferPx"
770+
[orientation]="orientation" [style.height.px]="viewportHeight"
771+
[style.width.px]="viewportWidth" (scrolledIndexChange)="scrolledToIndex = $event">
757772
<div class="item"
758773
*cdkVirtualFor="let item of items; let i = index; trackBy: trackBy; \
759774
templateCacheSize: templateCacheSize"
@@ -782,7 +797,8 @@ class FixedSizeVirtualScrollWithRtlDirection {
782797
@Input() viewportSize = 200;
783798
@Input() viewportCrossSize = 100;
784799
@Input() itemSize = 50;
785-
@Input() bufferSize = 0;
800+
@Input() minBufferPx = 0;
801+
@Input() maxBufferPx = 0;
786802
@Input() items = Array(10).fill(0).map((_, i) => i);
787803
@Input() trackBy: TrackByFunction<number>;
788804
@Input() templateCacheSize = 20;

src/cdk/scrolling/virtual-scroll-viewport.ts

Lines changed: 34 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import {Directionality} from '@angular/cdk/bidi';
1010
import {ListRange} from '@angular/cdk/collections';
11-
import {supportsScrollBehavior} from '@angular/cdk/platform';
1211
import {
1312
ChangeDetectionStrategy,
1413
ChangeDetectorRef,
@@ -24,8 +23,10 @@ import {
2423
ViewChild,
2524
ViewEncapsulation,
2625
} from '@angular/core';
27-
import {animationFrameScheduler, fromEvent, Observable, Subject} from 'rxjs';
26+
import {animationFrameScheduler, Observable, Subject} from 'rxjs';
2827
import {sampleTime, startWith, takeUntil} from 'rxjs/operators';
28+
import {ScrollDispatcher} from './scroll-dispatcher';
29+
import {CdkScrollable, ExtendedScrollToOptions} from './scrollable';
2930
import {CdkVirtualForOf} from './virtual-for-of';
3031
import {VIRTUAL_SCROLL_STRATEGY, VirtualScrollStrategy} from './virtual-scroll-strategy';
3132

@@ -50,7 +51,7 @@ function rangesEqual(r1: ListRange, r2: ListRange): boolean {
5051
encapsulation: ViewEncapsulation.None,
5152
changeDetection: ChangeDetectionStrategy.OnPush,
5253
})
53-
export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
54+
export class CdkVirtualScrollViewport extends CdkScrollable implements OnInit, OnDestroy {
5455
/** Emits when the viewport is detached from a CdkVirtualForOf. */
5556
private _detachedSubject = new Subject<void>();
5657

@@ -67,7 +68,7 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
6768
/** Emits when the index of the first element visible in the viewport changes. */
6869
@Output() scrolledIndexChange: Observable<number> = Observable.create(observer =>
6970
this._scrollStrategy.scrolledIndexChange.subscribe(index =>
70-
Promise.resolve().then(() => this._ngZone.run(() => observer.next(index)))));
71+
Promise.resolve().then(() => this.ngZone.run(() => observer.next(index)))));
7172

7273
/** The element that wraps the rendered content. */
7374
@ViewChild('contentWrapper') _contentWrapper: ElementRef<HTMLElement>;
@@ -113,9 +114,6 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
113114
*/
114115
private _renderedContentOffsetNeedsRewrite = false;
115116

116-
/** Observable that emits when the viewport is destroyed. */
117-
private _destroyed = new Subject<void>();
118-
119117
/** Whether there is a pending change detection cycle. */
120118
private _isChangeDetectionPending = false;
121119

@@ -124,27 +122,31 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
124122

125123
constructor(public elementRef: ElementRef<HTMLElement>,
126124
private _changeDetectorRef: ChangeDetectorRef,
127-
private _ngZone: NgZone,
125+
ngZone: NgZone,
128126
@Inject(VIRTUAL_SCROLL_STRATEGY) private _scrollStrategy: VirtualScrollStrategy,
129-
@Optional() private _dir: Directionality) {}
127+
@Optional() dir: Directionality,
128+
scrollDispatcher: ScrollDispatcher) {
129+
super(elementRef, scrollDispatcher, ngZone, dir);
130+
}
130131

131132
ngOnInit() {
133+
super.ngOnInit();
134+
132135
// It's still too early to measure the viewport at this point. Deferring with a promise allows
133136
// the Viewport to be rendered with the correct size before we measure. We run this outside the
134137
// zone to avoid causing more change detection cycles. We handle the change detection loop
135138
// ourselves instead.
136-
this._ngZone.runOutsideAngular(() => Promise.resolve().then(() => {
139+
this.ngZone.runOutsideAngular(() => Promise.resolve().then(() => {
137140
this._measureViewportSize();
138141
this._scrollStrategy.attach(this);
139142

140-
fromEvent(this.elementRef.nativeElement, 'scroll')
143+
this.elementScrolled()
141144
.pipe(
142145
// Start off with a fake scroll event so we properly detect our initial position.
143146
startWith(null!),
144147
// Sample the scroll stream at every animation frame. This way if there are multiple
145148
// scroll events in the same frame we only need to recheck our layout once.
146-
sampleTime(0, animationFrameScheduler),
147-
takeUntil(this._destroyed))
149+
sampleTime(0, animationFrameScheduler))
148150
.subscribe(() => this._scrollStrategy.onContentScrolled());
149151

150152
this._markChangeDetectionNeeded();
@@ -154,12 +156,12 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
154156
ngOnDestroy() {
155157
this.detach();
156158
this._scrollStrategy.detach();
157-
this._destroyed.next();
158159

159160
// Complete all subjects
160161
this._renderedRangeSubject.complete();
161162
this._detachedSubject.complete();
162-
this._destroyed.complete();
163+
164+
super.ngOnDestroy();
163165
}
164166

165167
/** Attaches a `CdkVirtualForOf` to this viewport. */
@@ -171,7 +173,7 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
171173
// Subscribe to the data stream of the CdkVirtualForOf to keep track of when the data length
172174
// changes. Run outside the zone to avoid triggering change detection, since we're managing the
173175
// change detection loop ourselves.
174-
this._ngZone.runOutsideAngular(() => {
176+
this.ngZone.runOutsideAngular(() => {
175177
this._forOf = forOf;
176178
this._forOf.dataStream.pipe(takeUntil(this._detachedSubject)).subscribe(data => {
177179
const newLength = data.length;
@@ -244,7 +246,7 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
244246
setRenderedContentOffset(offset: number, to: 'to-start' | 'to-end' = 'to-start') {
245247
// For a horizontal viewport in a right-to-left language we need to translate along the x-axis
246248
// in the negative direction.
247-
const axisDirection = this.orientation == 'horizontal' && this._dir && this._dir.value == 'rtl'
249+
const axisDirection = this.orientation == 'horizontal' && this.dir && this.dir.value == 'rtl'
248250
? -1 : 1;
249251
const axis = this.orientation === 'horizontal' ? 'X' : 'Y';
250252
let transform = `translate${axis}(${Number(axisDirection * offset)}px)`;
@@ -280,23 +282,13 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
280282
* @param behavior The ScrollBehavior to use when scrolling. Default is behavior is `auto`.
281283
*/
282284
scrollToOffset(offset: number, behavior: ScrollBehavior = 'auto') {
283-
const viewportElement = this.elementRef.nativeElement;
284-
285-
// For a horizontal viewport in a right-to-left language we need to calculate what `scrollRight`
286-
// would be.
287-
offset = this.orientation == 'horizontal' && this._dir && this._dir.value == 'rtl' ?
288-
this._getRightOffsetAsLeftOffset(offset) : offset;
289-
290-
if (supportsScrollBehavior()) {
291-
const offsetDirection = this.orientation === 'horizontal' ? 'left' : 'top';
292-
viewportElement.scrollTo({[offsetDirection]: offset, behavior});
285+
const options: ExtendedScrollToOptions = {behavior};
286+
if (this.orientation === 'horizontal') {
287+
options.start = offset;
293288
} else {
294-
if (this.orientation === 'horizontal') {
295-
viewportElement.scrollLeft = offset;
296-
} else {
297-
viewportElement.scrollTop = offset;
298-
}
289+
options.top = offset;
299290
}
291+
this.scrollTo(options);
300292
}
301293

302294
/**
@@ -308,16 +300,14 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
308300
this._scrollStrategy.scrollToIndex(index, behavior);
309301
}
310302

311-
/** Gets the current scroll offset from the start of the viewport (in pixels). */
312-
measureScrollOffset(): number {
313-
if (this.orientation == 'horizontal') {
314-
const offset = this.elementRef.nativeElement.scrollLeft;
315-
// For a horizontal viewport in a right-to-left language we need to calculate what
316-
// `scrollRight` would be.
317-
return this._dir && this._dir.value == 'rtl' ?
318-
this._getRightOffsetAsLeftOffset(offset) : offset;
319-
}
320-
return this.elementRef.nativeElement.scrollTop;
303+
/**
304+
* Gets the current scroll offset from the start of the viewport (in pixels).
305+
* @param from The edge to measure the offset from. Defaults to 'top' in vertical mode and 'start'
306+
* in horizontal mode.
307+
*/
308+
measureScrollOffset(from?: 'top' | 'left' | 'right' | 'bottom' | 'start' | 'end'): number {
309+
return super.measureScrollOffset(
310+
from ? from : this.orientation === 'horizontal' ? 'start' : 'top');
321311
}
322312

323313
/** Measure the combined size of all of the rendered items. */
@@ -361,7 +351,7 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
361351
// properties sequentially we only have to run `_doChangeDetection` once at the end.
362352
if (!this._isChangeDetectionPending) {
363353
this._isChangeDetectionPending = true;
364-
this._ngZone.runOutsideAngular(() => Promise.resolve().then(() => {
354+
this.ngZone.runOutsideAngular(() => Promise.resolve().then(() => {
365355
this._doChangeDetection();
366356
}));
367357
}
@@ -374,7 +364,7 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
374364
// Apply changes to Angular bindings. Note: We must call `markForCheck` to run change detection
375365
// from the root, since the repeated items are content projected in. Calling `detectChanges`
376366
// instead does not properly check the projected content.
377-
this._ngZone.run(() => this._changeDetectorRef.markForCheck());
367+
this.ngZone.run(() => this._changeDetectorRef.markForCheck());
378368
// Apply the content transform. The transform can't be set via an Angular binding because
379369
// bypassSecurityTrustStyle is banned in Google. However the value is safe, it's composed of
380370
// string literals, a variable that can only be 'X' or 'Y', and user input that is run through
@@ -387,9 +377,4 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
387377
fn();
388378
}
389379
}
390-
391-
/** Expresses an offset from the right as the equivalent offset from the left. */
392-
private _getRightOffsetAsLeftOffset(offset: number): number {
393-
return Math.max(0, this._totalContentSize - this._viewportSize - offset);
394-
}
395380
}

0 commit comments

Comments
 (0)