Skip to content

Commit 6ddf015

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 6ddf015

File tree

1 file changed

+40
-23
lines changed

1 file changed

+40
-23
lines changed

src/cdk/table/table.ts

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -297,14 +297,17 @@ export class CdkTable<T>
297297
protected readonly _changeDetectorRef = inject(ChangeDetectorRef);
298298
protected readonly _elementRef = inject(ElementRef);
299299
protected readonly _dir = inject(Directionality, {optional: true});
300-
private _platform = inject(Platform);
300+
private readonly _platform = inject(Platform);
301301
protected _viewRepeater: _ViewRepeater<T, RenderRow<T>, RowContext<T>>;
302302
private readonly _viewportRuler = inject(ViewportRuler);
303-
private _injector = inject(Injector);
304-
private _virtualScrollViewport = inject(CDK_VIRTUAL_SCROLL_VIEWPORT, {optional: true});
303+
private readonly _injector = inject(Injector);
305304
private _positionListener =
306305
inject(STICKY_POSITIONING_LISTENER, {optional: true}) ||
307306
inject(STICKY_POSITIONING_LISTENER, {optional: true, skipSelf: true});
307+
private readonly _virtualScrollViewport = inject(CDK_VIRTUAL_SCROLL_VIEWPORT, {
308+
optional: true,
309+
host: true,
310+
});
308311

309312
private _document = inject(DOCUMENT);
310313

@@ -467,6 +470,14 @@ export class CdkTable<T>
467470
/** Emits when the footer rows sticky state changes. */
468471
private readonly _footerRowStickyUpdates = new Subject<StickyUpdate>();
469472

473+
/**
474+
* Whether to explicitly disable virtual scrolling even if there is a virtual scroll viewport
475+
* parent. This can't be changed externally, whereas internally it is turned into an input that
476+
* we use to opt out existing apps that were implementing virtual scroll before we added support
477+
* for it.
478+
*/
479+
private readonly _disableVirtualScrolling = false;
480+
470481
/** Aria role to apply to the table's cells based on the table's own role. */
471482
_getCellRole(): string | null {
472483
// Perform this lazily in case the table's role was updated by a directive after construction.
@@ -565,7 +576,7 @@ export class CdkTable<T>
565576
get fixedLayout(): boolean {
566577
// Require a fixed layout when virtual scrolling is enabled, otherwise
567578
// the element the header can jump around as the user is scrolling.
568-
return this._virtualScrollViewport ? true : this._fixedLayout;
579+
return this._virtualScrollEnabled() ? true : this._fixedLayout;
569580
}
570581
set fixedLayout(value: boolean) {
571582
this._fixedLayout = value;
@@ -595,7 +606,10 @@ export class CdkTable<T>
595606
*
596607
* @docs-private
597608
*/
598-
readonly viewChange: BehaviorSubject<ListRange>;
609+
readonly viewChange: BehaviorSubject<ListRange> = new BehaviorSubject({
610+
start: 0,
611+
end: Number.MAX_VALUE,
612+
});
599613

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

639653
this._isServer = !this._platform.isBrowser;
640654
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-
});
645655

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

658664
ngOnInit() {
@@ -668,9 +674,14 @@ export class CdkTable<T>
668674

669675
ngAfterContentInit() {
670676
this._viewRepeater =
671-
this.recycleRows || this._virtualScrollViewport
677+
this.recycleRows || this._virtualScrollEnabled()
672678
? new _RecycleViewRepeaterStrategy()
673679
: new _DisposeViewRepeaterStrategy();
680+
681+
if (this._virtualScrollEnabled()) {
682+
this._setupVirtualScrolling(this._virtualScrollViewport!);
683+
}
684+
674685
this._hasInitialized = true;
675686
}
676687

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

10471061
// Store the cache and create a new one. Any re-used RenderRow objects will be moved into the
10481062
// new cache while unused ones can be picked up by garbage collection.
10491063
const prevCachedRenderRows = this._cachedRenderRowsMap;
10501064
this._cachedRenderRowsMap = new Map();
10511065

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

10621072
if (!this._cachedRenderRowsMap.has(data)) {
@@ -1480,6 +1490,9 @@ export class CdkTable<T>
14801490
const virtualScrollScheduler =
14811491
typeof requestAnimationFrame !== 'undefined' ? animationFrameScheduler : asapScheduler;
14821492

1493+
// Render nothing since the virtual scroll viewport will take over.
1494+
this.viewChange.next({start: 0, end: 0});
1495+
14831496
// Forward the rendered range computed by the virtual scroll viewport to the table.
14841497
viewport.renderedRangeStream
14851498
// We need the scheduler here, because the virtual scrolling module uses an identical
@@ -1629,6 +1642,10 @@ export class CdkTable<T>
16291642
const endRect = lastNode?.getBoundingClientRect?.();
16301643
return startRect && endRect ? endRect.bottom - startRect.top : 0;
16311644
}
1645+
1646+
private _virtualScrollEnabled(): boolean {
1647+
return !this._disableVirtualScrolling && this._virtualScrollViewport != null;
1648+
}
16321649
}
16331650

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

0 commit comments

Comments
 (0)