Skip to content

virtual-scroll: emit the currently scrolled to index #12381

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Jul 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import {coerceNumberProperty} from '@angular/cdk/coercion';
import {ListRange} from '@angular/cdk/collections';
import {Directive, forwardRef, Input, OnChanges} from '@angular/core';
import {Observable} from 'rxjs';
import {VIRTUAL_SCROLL_STRATEGY, VirtualScrollStrategy} from './virtual-scroll-strategy';
import {CdkVirtualScrollViewport} from './virtual-scroll-viewport';

Expand Down Expand Up @@ -65,6 +66,13 @@ export class ItemSizeAverager {

/** Virtual scrolling strategy for lists with items of unknown or dynamic size. */
export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
/** @docs-private Implemented as part of VirtualScrollStrategy. */
scrolledIndexChange = Observable.create(() => {
// TODO(mmalerba): Implement.
throw Error('cdk-virtual-scroll: scrolledIndexChange is currently not supported for the' +
' autosize scroll strategy');
});

/** The attached viewport. */
private _viewport: CdkVirtualScrollViewport | null = null;

Expand Down Expand Up @@ -154,7 +162,7 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
/** Scroll to the offset for the given index. */
scrollToIndex(): void {
// TODO(mmalerba): Implement.
throw new Error('cdk-virtual-scroll: scrollToIndex is currently not supported for the autosize'
throw Error('cdk-virtual-scroll: scrollToIndex is currently not supported for the autosize'
+ ' scroll strategy');
}

Expand Down
10 changes: 10 additions & 0 deletions src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,19 @@
import {coerceNumberProperty} from '@angular/cdk/coercion';
import {ListRange} from '@angular/cdk/collections';
import {Directive, forwardRef, Input, OnChanges} from '@angular/core';
import {Observable, Subject} from 'rxjs';
import {distinctUntilChanged} from 'rxjs/operators';
import {VIRTUAL_SCROLL_STRATEGY, VirtualScrollStrategy} from './virtual-scroll-strategy';
import {CdkVirtualScrollViewport} from './virtual-scroll-viewport';


/** Virtual scrolling strategy for lists with items of known fixed size. */
export class FixedSizeVirtualScrollStrategy implements VirtualScrollStrategy {
private _scrolledIndexChange = new Subject<number>();

/** @docs-private Implemented as part of VirtualScrollStrategy. */
scrolledIndexChange: Observable<number> = this._scrolledIndexChange.pipe(distinctUntilChanged());

/** The attached viewport. */
private _viewport: CdkVirtualScrollViewport | null = null;

Expand Down Expand Up @@ -45,6 +52,7 @@ export class FixedSizeVirtualScrollStrategy implements VirtualScrollStrategy {

/** Detaches this scroll strategy from the currently attached viewport. */
detach() {
this._scrolledIndexChange.complete();
this._viewport = null;
}

Expand Down Expand Up @@ -113,6 +121,8 @@ export class FixedSizeVirtualScrollStrategy implements VirtualScrollStrategy {
this._bufferSize);
this._viewport.setRenderedRange(range);
this._viewport.setRenderedContentOffset(this._itemSize * range.start);

this._scrolledIndexChange.next(firstVisibleIndex);
}

/**
Expand Down
6 changes: 5 additions & 1 deletion src/cdk-experimental/scrolling/virtual-scroll-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/

import {CdkVirtualScrollViewport} from './virtual-scroll-viewport';
import {InjectionToken} from '@angular/core';
import {Observable} from 'rxjs';
import {CdkVirtualScrollViewport} from './virtual-scroll-viewport';


/** The injection token used to specify the virtual scrolling strategy. */
Expand All @@ -17,6 +18,9 @@ export const VIRTUAL_SCROLL_STRATEGY =

/** A strategy that dictates which items should be rendered in the viewport. */
export interface VirtualScrollStrategy {
/** Emits when the index of the first element visible in the viewport changes. */
scrolledIndexChange: Observable<number>;

/**
* Attaches this scroll strategy to a viewport.
* @param viewport The viewport to attach this strategy to.
Expand Down
20 changes: 19 additions & 1 deletion src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,21 @@ describe('CdkVirtualScrollViewport', () => {
expect(viewport.getRenderedRange()).toEqual({start: 2, end: 6});
}));

it('should output scrolled index', fakeAsync(() => {
finishInit(fixture);
triggerScroll(viewport, testComponent.itemSize * 2 - 1);
fixture.detectChanges();
flush();

expect(testComponent.scrolledToIndex).toBe(1);

triggerScroll(viewport, testComponent.itemSize * 2);
fixture.detectChanges();
flush();

expect(testComponent.scrolledToIndex).toBe(2);
}));

it('should update viewport as user scrolls down', fakeAsync(() => {
finishInit(fixture);

Expand Down Expand Up @@ -608,7 +623,8 @@ function triggerScroll(viewport: CdkVirtualScrollViewport, offset?: number) {
template: `
<cdk-virtual-scroll-viewport
[itemSize]="itemSize" [bufferSize]="bufferSize" [orientation]="orientation"
[style.height.px]="viewportHeight" [style.width.px]="viewportWidth">
[style.height.px]="viewportHeight" [style.width.px]="viewportWidth"
(scrolledIndexChange)="scrolledToIndex = $event">
<div class="item"
*cdkVirtualFor="let item of items; let i = index; trackBy: trackBy; \
templateCacheSize: templateCacheSize"
Expand Down Expand Up @@ -642,6 +658,8 @@ class FixedSizeVirtualScroll {
@Input() trackBy;
@Input() templateCacheSize = 20;

scrolledToIndex = 0;

get viewportWidth() {
return this.orientation == 'horizontal' ? this.viewportSize : this.viewportCrossSize;
}
Expand Down
19 changes: 18 additions & 1 deletion src/cdk-experimental/scrolling/virtual-scroll-viewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ import {
NgZone,
OnDestroy,
OnInit,
Output,
ViewChild,
ViewEncapsulation,
} from '@angular/core';
import {animationFrameScheduler, fromEvent, Observable, Subject} from 'rxjs';
import {sampleTime, takeUntil} from 'rxjs/operators';
import {sample, sampleTime, takeUntil} from 'rxjs/operators';
import {CdkVirtualForOf} from './virtual-for-of';
import {VIRTUAL_SCROLL_STRATEGY, VirtualScrollStrategy} from './virtual-scroll-strategy';

Expand Down Expand Up @@ -54,9 +55,22 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
/** Emits when the rendered range changes. */
private _renderedRangeSubject = new Subject<ListRange>();

/** Emits when a change detection cycle completes. */
private _changeDetectionComplete = new Subject<void>();

/** The direction the viewport scrolls. */
@Input() orientation: 'horizontal' | 'vertical' = 'vertical';

// Note: we don't use the typical EventEmitter here because we need to subscribe to the scroll
// strategy lazily (i.e. only if the user is actually listening to the events). We do this because
// depending on how the strategy calculates the scrolled index, it may come at a cost to
// performance.
/** Emits when the index of the first element visible in the viewport changes. */
@Output() scrolledIndexChange: Observable<number> =
Observable.create(observer => this._scrollStrategy.scrolledIndexChange
.pipe(sample(this._changeDetectionComplete))
.subscribe(observer));

/** The element that wraps the rendered content. */
@ViewChild('contentWrapper') _contentWrapper: ElementRef<HTMLElement>;

Expand Down Expand Up @@ -139,6 +153,7 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
// Complete all subjects
this._renderedRangeSubject.complete();
this._detachedSubject.complete();
this._changeDetectionComplete.complete();
this._destroyed.complete();
}

Expand Down Expand Up @@ -363,5 +378,7 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
for (const fn of runAfterChangeDetection) {
fn();
}

this._ngZone.run(() => this._changeDetectionComplete.next());
}
}
6 changes: 5 additions & 1 deletion src/demo-app/virtual-scroll/virtual-scroll-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,12 @@ <h2>Fixed size</h2>
<button mat-button (click)="viewport1.scrollToIndex(scrollToIndex, scrollToBehavior)">
Go to index
</button>
<p>
Currently scrolled to item: {{scrolledIndex}}
</p>

<cdk-virtual-scroll-viewport class="demo-viewport" [itemSize]="50" #viewport1>
<cdk-virtual-scroll-viewport class="demo-viewport" [itemSize]="50" #viewport1
(scrolledIndexChange)="scrolled($event)">
<div *cdkVirtualFor="let size of fixedSizeData; let i = index" class="demo-item"
[style.height.px]="size">
Item #{{i}} - ({{size}}px)
Expand Down
5 changes: 5 additions & 0 deletions src/demo-app/virtual-scroll/virtual-scroll-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export class VirtualScrollDemo {
scrollToOffset = 0;
scrollToIndex = 0;
scrollToBehavior: ScrollBehavior = 'auto';
scrolledIndex = 0;
fixedSizeData = Array(10000).fill(50);
increasingSizeData = Array(10000).fill(0).map((_, i) => (1 + Math.floor(i / 1000)) * 20);
decreasingSizeData = Array(10000).fill(0)
Expand Down Expand Up @@ -110,4 +111,8 @@ export class VirtualScrollDemo {
return 0;
}));
}

scrolled(index: number) {
this.scrolledIndex = index;
}
}