@@ -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