Skip to content

Commit 21171f3

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 1697c4f commit 21171f3

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,
@@ -1489,26 +1489,15 @@ export class CdkTable<T>
14891489

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

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

1533-
combineLatest([offsetFromTopStream, this._footerRowStickyUpdates])
1522+
combineLatest([viewport.renderedContentOffset, this._footerRowStickyUpdates])
15341523
.pipe(takeUntil(this._onDestroy))
15351524
.subscribe(([offsetFromTop, update]) => {
15361525
if (!update.sizes || !update.offsets || !update.elements) {
@@ -1594,6 +1583,51 @@ export class CdkTable<T>
15941583

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

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

0 commit comments

Comments
 (0)