Skip to content

Commit cd1e183

Browse files
committed
refactor(cdk/table): implement range size measurement
Implements the `measureRangeSize` method so the CDK table is compatible with auto-sizing.
1 parent d80ec1b commit cd1e183

File tree

2 files changed

+58
-22
lines changed

2 files changed

+58
-22
lines changed

goldens/cdk/table/index.api.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ export class CdkRowDef<T> extends BaseRowDef {
291291
}
292292

293293
// @public
294-
export class CdkTable<T> implements AfterContentInit, AfterContentChecked, CollectionViewer, OnDestroy, OnInit {
294+
export class CdkTable<T> implements AfterContentInit, AfterContentChecked, CollectionViewer, OnDestroy, OnInit, StickyPositioningListener {
295295
constructor(...args: unknown[]);
296296
addColumnDef(columnDef: CdkColumnDef): void;
297297
addFooterRowDef(footerRowDef: CdkFooterRowDef): void;
@@ -307,6 +307,8 @@ export class CdkTable<T> implements AfterContentInit, AfterContentChecked, Colle
307307
protected _data: readonly T[] | undefined;
308308
get dataSource(): CdkTableDataSourceInput<T>;
309309
set dataSource(dataSource: CdkTableDataSourceInput<T>);
310+
readonly _dataSourceChanges: Subject<CdkTableDataSourceInput<T>>;
311+
readonly _dataStream: Subject<readonly T[]>;
310312
// (undocumented)
311313
protected readonly _differs: IterableDiffers;
312314
// (undocumented)
@@ -352,22 +354,22 @@ export class CdkTable<T> implements AfterContentInit, AfterContentChecked, Colle
352354
removeFooterRowDef(footerRowDef: CdkFooterRowDef): void;
353355
removeHeaderRowDef(headerRowDef: CdkHeaderRowDef): void;
354356
removeRowDef(rowDef: CdkRowDef<T>): void;
357+
protected _renderedRange?: ListRange;
355358
renderRows(): void;
356359
// (undocumented)
357360
_rowOutlet: DataRowOutlet;
358361
setNoDataRow(noDataRow: CdkNoDataRow | null): void;
362+
stickyColumnsUpdated(update: StickyUpdate): void;
359363
protected stickyCssClass: string;
360-
// (undocumented)
361-
protected readonly _stickyPositioningListener: StickyPositioningListener;
364+
stickyEndColumnsUpdated(update: StickyUpdate): void;
365+
stickyFooterRowsUpdated(update: StickyUpdate): void;
366+
stickyHeaderRowsUpdated(update: StickyUpdate): void;
362367
get trackBy(): TrackByFunction<T>;
363368
set trackBy(fn: TrackByFunction<T>);
364369
updateStickyColumnStyles(): void;
365370
updateStickyFooterRowStyles(): void;
366371
updateStickyHeaderRowStyles(): void;
367-
readonly viewChange: BehaviorSubject<{
368-
start: number;
369-
end: number;
370-
}>;
372+
readonly viewChange: BehaviorSubject<ListRange>;
371373
// (undocumented)
372374
protected _viewRepeater: _ViewRepeater<T, RenderRow<T>, RowContext<T>>;
373375
// (undocumented)

src/cdk/table/table.ts

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ import {
6767
Subject,
6868
Subscription,
6969
} from 'rxjs';
70-
import {auditTime, filter, map, takeUntil} from 'rxjs/operators';
70+
import {auditTime, takeUntil} from 'rxjs/operators';
7171
import {CdkColumnDef} from './cell';
7272
import {
7373
BaseRowDef,
@@ -1490,26 +1490,15 @@ export class CdkTable<T>
14901490

14911491
viewport.attach({
14921492
dataStream: this._dataStream,
1493-
measureRangeSize: () => {
1494-
// TODO(crisbeto): implement this method so autosizing works.
1495-
if (typeof ngDevMode === 'undefined' || ngDevMode) {
1496-
throw new Error('autoSize is not supported for tables with virtual scroll enabled.');
1497-
}
1498-
return 0;
1499-
},
1493+
measureRangeSize: (range, orientation) => this._measureRangeSize(range, orientation),
15001494
});
15011495

1502-
const offsetFromTopStream = this.viewChange.pipe(
1503-
map(() => viewport.getOffsetToRenderedContentStart()),
1504-
filter(offset => offset !== null),
1505-
);
1506-
15071496
// The `StyickyStyler` sticks elements by applying a `top` or `bottom` position offset to
15081497
// them. However, the virtual scroll viewport applies a `translateY` offset to a container
15091498
// div that encapsulates the table. The translation causes the rows to also be offset by the
15101499
// distance from the top of the scroll viewport in addition to their `top` offset. This logic
15111500
// negates the translation to move the rows to their correct positions.
1512-
combineLatest([offsetFromTopStream, this._headerRowStickyUpdates])
1501+
combineLatest([viewport.renderedContentOffset, this._headerRowStickyUpdates])
15131502
.pipe(takeUntil(this._onDestroy))
15141503
.subscribe(([offsetFromTop, update]) => {
15151504
if (!update.sizes || !update.offsets || !update.elements) {
@@ -1531,7 +1520,7 @@ export class CdkTable<T>
15311520
}
15321521
});
15331522

1534-
combineLatest([offsetFromTopStream, this._footerRowStickyUpdates])
1523+
combineLatest([viewport.renderedContentOffset, this._footerRowStickyUpdates])
15351524
.pipe(takeUntil(this._onDestroy))
15361525
.subscribe(([offsetFromTop, update]) => {
15371526
if (!update.sizes || !update.offsets || !update.elements) {
@@ -1595,6 +1584,51 @@ export class CdkTable<T>
15951584

15961585
this._changeDetectorRef.markForCheck();
15971586
}
1587+
1588+
/**
1589+
* Measures the size of the rendered range in the table.
1590+
* This is used for virtual scrolling when auto-sizing is enabled.
1591+
*/
1592+
private _measureRangeSize(range: ListRange, orientation: 'horizontal' | 'vertical'): number {
1593+
if (range.start >= range.end || orientation !== 'vertical') {
1594+
return 0;
1595+
}
1596+
1597+
const renderedRange = this.viewChange.value;
1598+
const viewContainerRef = this._rowOutlet.viewContainer;
1599+
1600+
if (
1601+
(range.start < renderedRange.start || range.end > renderedRange.end) &&
1602+
(typeof ngDevMode === 'undefined' || ngDevMode)
1603+
) {
1604+
throw Error(`Error: attempted to measure an item that isn't rendered.`);
1605+
}
1606+
1607+
const renderedStartIndex = range.start - renderedRange.start;
1608+
const rangeLen = range.end - range.start;
1609+
let firstNode: HTMLElement | undefined;
1610+
let lastNode: HTMLElement | undefined;
1611+
1612+
for (let i = 0; i < rangeLen; i++) {
1613+
const view = viewContainerRef.get(i + renderedStartIndex) as EmbeddedViewRef<unknown> | null;
1614+
if (view && view.rootNodes.length) {
1615+
firstNode = lastNode = view.rootNodes[0];
1616+
break;
1617+
}
1618+
}
1619+
1620+
for (let i = rangeLen - 1; i > -1; i--) {
1621+
const view = viewContainerRef.get(i + renderedStartIndex) as EmbeddedViewRef<unknown> | null;
1622+
if (view && view.rootNodes.length) {
1623+
lastNode = view.rootNodes[view.rootNodes.length - 1];
1624+
break;
1625+
}
1626+
}
1627+
1628+
const startRect = firstNode?.getBoundingClientRect?.();
1629+
const endRect = lastNode?.getBoundingClientRect?.();
1630+
return startRect && endRect ? endRect.bottom - startRect.top : 0;
1631+
}
15981632
}
15991633

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

0 commit comments

Comments
 (0)