Skip to content

Commit eccaecd

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 b85e5f5 commit eccaecd

File tree

1 file changed

+40
-21
lines changed

1 file changed

+40
-21
lines changed

src/cdk/table/table.ts

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,12 @@ export class CdkTable<T>
300300
protected _viewRepeater: _ViewRepeater<T, RenderRow<T>, RowContext<T>>;
301301
private readonly _viewportRuler = inject(ViewportRuler);
302302
private _injector = inject(Injector);
303-
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+
});
304309
private _positionListener =
305310
inject(STICKY_POSITIONING_LISTENER, {optional: true}) ||
306311
inject(STICKY_POSITIONING_LISTENER, {optional: true, skipSelf: true});
@@ -466,6 +471,14 @@ export class CdkTable<T>
466471
/** Emits when the footer rows sticky state changes. */
467472
private readonly _footerRowStickyUpdates = new Subject<StickyUpdate>();
468473

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+
469482
/** Aria role to apply to the table's cells based on the table's own role. */
470483
_getCellRole(): string | null {
471484
// Perform this lazily in case the table's role was updated by a directive after construction.
@@ -564,7 +577,7 @@ export class CdkTable<T>
564577
get fixedLayout(): boolean {
565578
// Require a fixed layout when virtual scrolling is enabled, otherwise
566579
// the element the header can jump around as the user is scrolling.
567-
return this._virtualScrollViewport ? true : this._fixedLayout;
580+
return this._virtualScrollEnabled() ? true : this._fixedLayout;
568581
}
569582
set fixedLayout(value: boolean) {
570583
this._fixedLayout = value;
@@ -594,7 +607,10 @@ export class CdkTable<T>
594607
*
595608
* @docs-private
596609
*/
597-
readonly viewChange: BehaviorSubject<ListRange>;
610+
readonly viewChange: BehaviorSubject<ListRange> = new BehaviorSubject({
611+
start: 0,
612+
end: Number.MAX_VALUE,
613+
});
598614

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

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

645657
// Set up the trackBy function so that it uses the `RenderRow` as its identity by default. If
646658
// the user has provided a custom trackBy, return the result of that function as evaluated
647659
// with the values of the `RenderRow`'s data and index.
648660
this._dataDiffer = this._differs.find([]).create((_i: number, dataRow: RenderRow<T>) => {
649661
return this.trackBy ? this.trackBy(dataRow.dataIndex, dataRow.data) : dataRow;
650662
});
651-
652-
if (this._virtualScrollViewport) {
653-
this._setupVirtualScrolling(this._virtualScrollViewport);
654-
}
655663
}
656664

657665
ngOnInit() {
@@ -667,9 +675,14 @@ export class CdkTable<T>
667675

668676
ngAfterContentInit() {
669677
this._viewRepeater =
670-
this.recycleRows || this._virtualScrollViewport
678+
this.recycleRows || this._virtualScrollEnabled()
671679
? new _RecycleViewRepeaterStrategy()
672680
: new _DisposeViewRepeaterStrategy();
681+
682+
if (this._virtualScrollEnabled()) {
683+
this._setupVirtualScrolling(this._virtualScrollViewport!);
684+
}
685+
673686
this._hasInitialized = true;
674687
}
675688

@@ -1038,24 +1051,23 @@ export class CdkTable<T>
10381051
* so that the differ equates their references.
10391052
*/
10401053
private _getAllRenderRows(): RenderRow<T>[] {
1041-
const dataWithinRange = this._renderedRange
1042-
? (this._data || []).slice(this._renderedRange.start, this._renderedRange.end)
1043-
: [];
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+
10441059
const renderRows: RenderRow<T>[] = [];
1060+
const end = Math.min(this._data.length, this._renderedRange.end);
10451061

10461062
// Store the cache and create a new one. Any re-used RenderRow objects will be moved into the
10471063
// new cache while unused ones can be picked up by garbage collection.
10481064
const prevCachedRenderRows = this._cachedRenderRowsMap;
10491065
this._cachedRenderRowsMap = new Map();
10501066

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

10611073
if (!this._cachedRenderRowsMap.has(data)) {
@@ -1479,6 +1491,9 @@ export class CdkTable<T>
14791491
const virtualScrollScheduler =
14801492
typeof requestAnimationFrame !== 'undefined' ? animationFrameScheduler : asapScheduler;
14811493

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

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

0 commit comments

Comments
 (0)