mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
virtual-scroll: emit the currently scrolled to index (#12381)
Note: `(scrolledIndexChange)` currently does not work with the `AutoSizeVirtualScrollStrategy`. Support for this will be added in a future PR.
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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. */
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
@@ -642,6 +658,8 @@ class FixedSizeVirtualScroll {
|
||||
@Input() trackBy;
|
||||
@Input() templateCacheSize = 20;
|
||||
|
||||
scrolledToIndex = 0;
|
||||
|
||||
get viewportWidth() {
|
||||
return this.orientation == 'horizontal' ? this.viewportSize : this.viewportCrossSize;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -363,5 +378,7 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
|
||||
for (const fn of runAfterChangeDetection) {
|
||||
fn();
|
||||
}
|
||||
|
||||
this._ngZone.run(() => this._changeDetectionComplete.next());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,8 +56,12 @@
|
||||
<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)
|
||||
|
||||
@@ -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)
|
||||
@@ -110,4 +111,8 @@ export class VirtualScrollDemo {
|
||||
return 0;
|
||||
}));
|
||||
}
|
||||
|
||||
scrolled(index: number) {
|
||||
this.scrolledIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user