Skip to content

Commit 63ad51b

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 63ad51b

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
@@ -301,7 +301,12 @@ export class CdkTable<T>
301301
protected _viewRepeater: _ViewRepeater<T, RenderRow<T>, RowContext<T>>;
302302
private readonly _viewportRuler = inject(ViewportRuler);
303303
private _injector = inject(Injector);
304-
private _virtualScrollViewport = inject(CDK_VIRTUAL_SCROLL_VIEWPORT, {optional: true});
304+
private _virtualScrollViewport = inject(CDK_VIRTUAL_SCROLL_VIEWPORT, {
305+
optional: true,
306+
// Virtual scrolling can only be enabled by a viewport in
307+
// the same host, don't try to resolve in parent components.
308+
host: true,
309+
});
305310
private _positionListener =
306311
inject(STICKY_POSITIONING_LISTENER, {optional: true}) ||
307312
inject(STICKY_POSITIONING_LISTENER, {optional: true, skipSelf: true});
@@ -467,6 +472,14 @@ export class CdkTable<T>
467472
/** Emits when the footer rows sticky state changes. */
468473
private readonly _footerRowStickyUpdates = new Subject<StickyUpdate>();
469474

475+
/**
476+
* Whether to explicitly disable virtual scrolling even if there is a virtual scroll viewport
477+
* parent. This can't be changed externally, whereas internally it is turned into an input that
478+
* we use to opt out existing apps that were implementing virtual scroll before we added support
479+
* for it.
480+
*/
481+
private readonly _disableVirtualScrolling = false;
482+
470483
/** Aria role to apply to the table's cells based on the table's own role. */
471484
_getCellRole(): string | null {
472485
// Perform this lazily in case the table's role was updated by a directive after construction.
@@ -565,7 +578,7 @@ export class CdkTable<T>
565578
get fixedLayout(): boolean {
566579
// Require a fixed layout when virtual scrolling is enabled, otherwise
567580
// the element the header can jump around as the user is scrolling.
568-
return this._virtualScrollViewport ? true : this._fixedLayout;
581+
return this._virtualScrollEnabled() ? true : this._fixedLayout;
569582
}
570583
set fixedLayout(value: boolean) {
571584
this._fixedLayout = value;
@@ -595,7 +608,10 @@ export class CdkTable<T>
595608
*
596609
* @docs-private
597610
*/
598-
readonly viewChange: BehaviorSubject<ListRange>;
611+
readonly viewChange: BehaviorSubject<ListRange> = new BehaviorSubject({
612+
start: 0,
613+
end: Number.MAX_VALUE,
614+
});
599615

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

639655
this._isServer = !this._platform.isBrowser;
640656
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-
});
645657

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

658666
ngOnInit() {
@@ -668,9 +676,14 @@ export class CdkTable<T>
668676

669677
ngAfterContentInit() {
670678
this._viewRepeater =
671-
this.recycleRows || this._virtualScrollViewport
679+
this.recycleRows || this._virtualScrollEnabled()
672680
? new _RecycleViewRepeaterStrategy()
673681
: new _DisposeViewRepeaterStrategy();
682+
683+
if (this._virtualScrollEnabled()) {
684+
this._setupVirtualScrolling(this._virtualScrollViewport!);
685+
}
686+
674687
this._hasInitialized = true;
675688
}
676689

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

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

1052-
if (!this._data) {
1053-
return renderRows;
1054-
}
1055-
10561068
// For each data object, get the list of rows that should be rendered, represented by the
10571069
// 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];
1070+
for (let i = this._renderedRange.start; i < end; i++) {
1071+
const data = this._data[i];
10601072
const renderRowsForData = this._getRenderRowsForData(data, i, prevCachedRenderRows.get(data));
10611073

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

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

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

0 commit comments

Comments
 (0)