Skip to content

Commit 010c3d8

Browse files
committed
refactor(cdk/table): add internal opt out for virtual scrolling
Adds an opt out that we can use internally to opt apps out of virtual scrolling. This allows us to keep the public API clean for the majority of users.
1 parent fa3ecd1 commit 010c3d8

File tree

1 file changed

+41
-23
lines changed

1 file changed

+41
-23
lines changed

src/cdk/table/table.ts

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,7 @@ export class NoDataRowOutlet implements RowOutlet {
199199
* @docs-private
200200
*/
201201
export interface RowContext<T>
202-
extends CdkCellOutletMultiRowContext<T>,
203-
CdkCellOutletRowContext<T> {}
202+
extends CdkCellOutletMultiRowContext<T>, CdkCellOutletRowContext<T> {}
204203

205204
/**
206205
* Class used to conveniently type the embedded view ref for rows with a context.
@@ -301,7 +300,12 @@ export class CdkTable<T>
301300
protected _viewRepeater: _ViewRepeater<T, RenderRow<T>, RowContext<T>>;
302301
private readonly _viewportRuler = inject(ViewportRuler);
303302
private _injector = inject(Injector);
304-
private _virtualScrollViewport = inject(CDK_VIRTUAL_SCROLL_VIEWPORT, {optional: true});
303+
private _virtualScrollViewport = inject(CDK_VIRTUAL_SCROLL_VIEWPORT, {
304+
optional: true,
305+
// Virtual scrolling can only be enabled by a viewport in
306+
// the same host, don't try to resolve in parent components.
307+
host: true,
308+
});
305309
private _positionListener =
306310
inject(STICKY_POSITIONING_LISTENER, {optional: true}) ||
307311
inject(STICKY_POSITIONING_LISTENER, {optional: true, skipSelf: true});
@@ -467,6 +471,14 @@ export class CdkTable<T>
467471
/** Emits when the footer rows sticky state changes. */
468472
private readonly _footerRowStickyUpdates = new Subject<StickyUpdate>();
469473

474+
/**
475+
* Whether to explicitly disable virtual scrolling even if there is a virtual scroll viewport
476+
* parent. This can't be changed externally, whereas internally it is turned into an input that
477+
* we use to opt out existing apps that were implementing virtual scroll before we added support
478+
* for it.
479+
*/
480+
private readonly _disableVirtualScrolling = false;
481+
470482
/** Aria role to apply to the table's cells based on the table's own role. */
471483
_getCellRole(): string | null {
472484
// Perform this lazily in case the table's role was updated by a directive after construction.
@@ -565,7 +577,7 @@ export class CdkTable<T>
565577
get fixedLayout(): boolean {
566578
// Require a fixed layout when virtual scrolling is enabled, otherwise
567579
// the element the header can jump around as the user is scrolling.
568-
return this._virtualScrollViewport ? true : this._fixedLayout;
580+
return this._virtualScrollEnabled() ? true : this._fixedLayout;
569581
}
570582
set fixedLayout(value: boolean) {
571583
this._fixedLayout = value;
@@ -595,7 +607,10 @@ export class CdkTable<T>
595607
*
596608
* @docs-private
597609
*/
598-
readonly viewChange: BehaviorSubject<ListRange>;
610+
readonly viewChange: BehaviorSubject<ListRange> = new BehaviorSubject({
611+
start: 0,
612+
end: Number.MAX_VALUE,
613+
});
599614

600615
// Outlets in the table's template where the header, data rows, and footer will be inserted.
601616
_rowOutlet: DataRowOutlet;
@@ -638,21 +653,13 @@ export class CdkTable<T>
638653

639654
this._isServer = !this._platform.isBrowser;
640655
this._isNativeHtmlTable = this._elementRef.nativeElement.nodeName === 'TABLE';
641-
this.viewChange = new BehaviorSubject<ListRange>({
642-
start: 0,
643-
end: this._virtualScrollViewport ? 0 : Number.MAX_VALUE,
644-
});
645656

646657
// Set up the trackBy function so that it uses the `RenderRow` as its identity by default. If
647658
// the user has provided a custom trackBy, return the result of that function as evaluated
648659
// with the values of the `RenderRow`'s data and index.
649660
this._dataDiffer = this._differs.find([]).create((_i: number, dataRow: RenderRow<T>) => {
650661
return this.trackBy ? this.trackBy(dataRow.dataIndex, dataRow.data) : dataRow;
651662
});
652-
653-
if (this._virtualScrollViewport) {
654-
this._setupVirtualScrolling(this._virtualScrollViewport);
655-
}
656663
}
657664

658665
ngOnInit() {
@@ -668,9 +675,14 @@ export class CdkTable<T>
668675

669676
ngAfterContentInit() {
670677
this._viewRepeater =
671-
this.recycleRows || this._virtualScrollViewport
678+
this.recycleRows || this._virtualScrollEnabled()
672679
? new _RecycleViewRepeaterStrategy()
673680
: new _DisposeViewRepeaterStrategy();
681+
682+
if (this._virtualScrollEnabled()) {
683+
this._setupVirtualScrolling(this._virtualScrollViewport!);
684+
}
685+
674686
this._hasInitialized = true;
675687
}
676688

@@ -1039,24 +1051,23 @@ export class CdkTable<T>
10391051
* so that the differ equates their references.
10401052
*/
10411053
private _getAllRenderRows(): RenderRow<T>[] {
1042-
const dataWithinRange = this._renderedRange
1043-
? (this._data || []).slice(this._renderedRange.start, this._renderedRange.end)
1044-
: [];
1054+
// Note: the `_data` is typed as an array, but some internal apps end up passing diffrent types.
1055+
if (!Array.isArray(this._data) || !this._renderedRange) {
1056+
return [];
1057+
}
1058+
10451059
const renderRows: RenderRow<T>[] = [];
1060+
const end = Math.min(this._data.length, this._renderedRange.end);
10461061

10471062
// Store the cache and create a new one. Any re-used RenderRow objects will be moved into the
10481063
// new cache while unused ones can be picked up by garbage collection.
10491064
const prevCachedRenderRows = this._cachedRenderRowsMap;
10501065
this._cachedRenderRowsMap = new Map();
10511066

1052-
if (!this._data) {
1053-
return renderRows;
1054-
}
1055-
10561067
// For each data object, get the list of rows that should be rendered, represented by the
10571068
// respective `RenderRow` object which is the pair of `data` and `CdkRowDef`.
1058-
for (let i = 0; i < dataWithinRange.length; i++) {
1059-
let data = dataWithinRange[i];
1069+
for (let i = this._renderedRange.start; i < end; i++) {
1070+
const data = this._data[i];
10601071
const renderRowsForData = this._getRenderRowsForData(data, i, prevCachedRenderRows.get(data));
10611072

10621073
if (!this._cachedRenderRowsMap.has(data)) {
@@ -1480,6 +1491,9 @@ export class CdkTable<T>
14801491
const virtualScrollScheduler =
14811492
typeof requestAnimationFrame !== 'undefined' ? animationFrameScheduler : asapScheduler;
14821493

1494+
// Render nothing since the virtual scroll viewport will take over.
1495+
this.viewChange.next({start: 0, end: 0});
1496+
14831497
// Forward the rendered range computed by the virtual scroll viewport to the table.
14841498
viewport.renderedRangeStream
14851499
// We need the scheduler here, because the virtual scrolling module uses an identical
@@ -1629,6 +1643,10 @@ export class CdkTable<T>
16291643
const endRect = lastNode?.getBoundingClientRect?.();
16301644
return startRect && endRect ? endRect.bottom - startRect.top : 0;
16311645
}
1646+
1647+
private _virtualScrollEnabled(): boolean {
1648+
return !this._disableVirtualScrolling && this._virtualScrollViewport != null;
1649+
}
16321650
}
16331651

16341652
/** Utility function that gets a merged list of the entries in an array and values of a Set. */

0 commit comments

Comments
 (0)