1010 * Directions that can be used when setting sticky positioning.
1111 * @docs -private
1212 */
13+ import { afterNextRender , Injector } from '@angular/core' ;
1314import { Direction } from '@angular/cdk/bidi' ;
1415import { _CoalescedStyleScheduler } from './coalesced-style-scheduler' ;
1516import { StickyPositioningListener } from './sticky-position-listener' ;
@@ -41,6 +42,7 @@ export class StickyStyler {
4142 private _stickyColumnsReplayTimeout : number | null = null ;
4243 private _cachedCellWidths : number [ ] = [ ] ;
4344 private readonly _borderCellCss : Readonly < { [ d in StickyDirection ] : string } > ;
45+ private _destroyed = false ;
4446
4547 /**
4648 * @param _isNativeHtmlTable Whether the sticky logic should be based on a table
@@ -64,6 +66,7 @@ export class StickyStyler {
6466 private _isBrowser = true ,
6567 private readonly _needsPositionStickyOnElement = true ,
6668 private readonly _positionListener ?: StickyPositioningListener ,
69+ private readonly _tableInjector ?: Injector ,
6770 ) {
6871 this . _borderCellCss = {
6972 'top' : `${ _stickCellCss } -border-elem-top` ,
@@ -92,18 +95,20 @@ export class StickyStyler {
9295 continue ;
9396 }
9497
95- elementsToClear . push ( row ) ;
96- for ( let i = 0 ; i < row . children . length ; i ++ ) {
97- elementsToClear . push ( row . children [ i ] as HTMLElement ) ;
98- }
98+ elementsToClear . push ( row , ...( row . children as HTMLCollectionOf < HTMLElement > ) ) ;
9999 }
100100
101101 // Coalesce with sticky row/column updates (and potentially other changes like column resize).
102- this . _coalescedStyleScheduler . schedule ( ( ) => {
103- for ( const element of elementsToClear ) {
104- this . _removeStickyStyle ( element , stickyDirections ) ;
105- }
106- } ) ;
102+ afterNextRender (
103+ {
104+ write : ( ) => {
105+ for ( const element of elementsToClear ) {
106+ this . _removeStickyStyle ( element , stickyDirections ) ;
107+ }
108+ } ,
109+ } ,
110+ { injector : this . _tableInjector } ,
111+ ) ;
107112 }
108113
109114 /**
@@ -147,54 +152,67 @@ export class StickyStyler {
147152 }
148153
149154 // Coalesce with sticky row updates (and potentially other changes like column resize).
150- this . _coalescedStyleScheduler . schedule ( ( ) => {
151- const firstRow = rows [ 0 ] ;
152- const numCells = firstRow . children . length ;
153- const cellWidths : number [ ] = this . _getCellWidths ( firstRow , recalculateCellWidths ) ;
154-
155- const startPositions = this . _getStickyStartColumnPositions ( cellWidths , stickyStartStates ) ;
156- const endPositions = this . _getStickyEndColumnPositions ( cellWidths , stickyEndStates ) ;
157-
158- const lastStickyStart = stickyStartStates . lastIndexOf ( true ) ;
159- const firstStickyEnd = stickyEndStates . indexOf ( true ) ;
160-
161- const isRtl = this . direction === 'rtl' ;
162- const start = isRtl ? 'right' : 'left' ;
163- const end = isRtl ? 'left' : 'right' ;
164-
165- for ( const row of rows ) {
166- for ( let i = 0 ; i < numCells ; i ++ ) {
167- const cell = row . children [ i ] as HTMLElement ;
168- if ( stickyStartStates [ i ] ) {
169- this . _addStickyStyle ( cell , start , startPositions [ i ] , i === lastStickyStart ) ;
155+ const firstRow = rows [ 0 ] ;
156+ const numCells = firstRow . children . length ;
157+
158+ const isRtl = this . direction === 'rtl' ;
159+ const start = isRtl ? 'right' : 'left' ;
160+ const end = isRtl ? 'left' : 'right' ;
161+
162+ const lastStickyStart = stickyStartStates . lastIndexOf ( true ) ;
163+ const firstStickyEnd = stickyEndStates . indexOf ( true ) ;
164+
165+ let cellWidths : number [ ] ;
166+ let startPositions : number [ ] ;
167+ let endPositions : number [ ] ;
168+
169+ afterNextRender (
170+ {
171+ earlyRead : ( ) => {
172+ cellWidths = this . _getCellWidths ( firstRow , recalculateCellWidths ) ;
173+
174+ startPositions = this . _getStickyStartColumnPositions ( cellWidths , stickyStartStates ) ;
175+ endPositions = this . _getStickyEndColumnPositions ( cellWidths , stickyEndStates ) ;
176+ } ,
177+ write : ( ) => {
178+ for ( const row of rows ) {
179+ for ( let i = 0 ; i < numCells ; i ++ ) {
180+ const cell = row . children [ i ] as HTMLElement ;
181+ if ( stickyStartStates [ i ] ) {
182+ this . _addStickyStyle ( cell , start , startPositions [ i ] , i === lastStickyStart ) ;
183+ }
184+
185+ if ( stickyEndStates [ i ] ) {
186+ this . _addStickyStyle ( cell , end , endPositions [ i ] , i === firstStickyEnd ) ;
187+ }
188+ }
170189 }
171190
172- if ( stickyEndStates [ i ] ) {
173- this . _addStickyStyle ( cell , end , endPositions [ i ] , i === firstStickyEnd ) ;
191+ if ( this . _positionListener ) {
192+ this . _positionListener . stickyColumnsUpdated ( {
193+ sizes :
194+ lastStickyStart === - 1
195+ ? [ ]
196+ : cellWidths
197+ . slice ( 0 , lastStickyStart + 1 )
198+ . map ( ( width , index ) => ( stickyStartStates [ index ] ? width : null ) ) ,
199+ } ) ;
200+ this . _positionListener . stickyEndColumnsUpdated ( {
201+ sizes :
202+ firstStickyEnd === - 1
203+ ? [ ]
204+ : cellWidths
205+ . slice ( firstStickyEnd )
206+ . map ( ( width , index ) =>
207+ stickyEndStates [ index + firstStickyEnd ] ? width : null ,
208+ )
209+ . reverse ( ) ,
210+ } ) ;
174211 }
175- }
176- }
177-
178- if ( this . _positionListener ) {
179- this . _positionListener . stickyColumnsUpdated ( {
180- sizes :
181- lastStickyStart === - 1
182- ? [ ]
183- : cellWidths
184- . slice ( 0 , lastStickyStart + 1 )
185- . map ( ( width , index ) => ( stickyStartStates [ index ] ? width : null ) ) ,
186- } ) ;
187- this . _positionListener . stickyEndColumnsUpdated ( {
188- sizes :
189- firstStickyEnd === - 1
190- ? [ ]
191- : cellWidths
192- . slice ( firstStickyEnd )
193- . map ( ( width , index ) => ( stickyEndStates [ index + firstStickyEnd ] ? width : null ) )
194- . reverse ( ) ,
195- } ) ;
196- }
197- } ) ;
212+ } ,
213+ } ,
214+ { injector : this . _tableInjector } ,
215+ ) ;
198216 }
199217
200218 /**
@@ -214,64 +232,70 @@ export class StickyStyler {
214232 return ;
215233 }
216234
217- // Coalesce with other sticky row updates (top/bottom), sticky columns updates
218- // (and potentially other changes like column resize).
219- this . _coalescedStyleScheduler . schedule ( ( ) => {
220- // If positioning the rows to the bottom, reverse their order when evaluating the sticky
221- // position such that the last row stuck will be "bottom: 0px" and so on. Note that the
222- // sticky states need to be reversed as well.
223- const rows = position === 'bottom' ? rowsToStick . slice ( ) . reverse ( ) : rowsToStick ;
224- const states = position === 'bottom' ? stickyStates . slice ( ) . reverse ( ) : stickyStates ;
225-
226- // Measure row heights all at once before adding sticky styles to reduce layout thrashing.
227- const stickyOffsets : number [ ] = [ ] ;
228- const stickyCellHeights : ( number | undefined ) [ ] = [ ] ;
229- const elementsToStick : HTMLElement [ ] [ ] = [ ] ;
230-
231- for ( let rowIndex = 0 , stickyOffset = 0 ; rowIndex < rows . length ; rowIndex ++ ) {
232- if ( ! states [ rowIndex ] ) {
233- continue ;
234- }
235+ // If positioning the rows to the bottom, reverse their order when evaluating the sticky
236+ // position such that the last row stuck will be "bottom: 0px" and so on. Note that the
237+ // sticky states need to be reversed as well.
238+ const rows = position === 'bottom' ? rowsToStick . slice ( ) . reverse ( ) : rowsToStick ;
239+ const states = position === 'bottom' ? stickyStates . slice ( ) . reverse ( ) : stickyStates ;
235240
236- stickyOffsets [ rowIndex ] = stickyOffset ;
237- const row = rows [ rowIndex ] ;
238- elementsToStick [ rowIndex ] = this . _isNativeHtmlTable
239- ? ( Array . from ( row . children ) as HTMLElement [ ] )
240- : [ row ] ;
241-
242- const height = this . _retrieveElementSize ( row ) . height ;
243- stickyOffset += height ;
244- stickyCellHeights [ rowIndex ] = height ;
245- }
241+ // Measure row heights all at once before adding sticky styles to reduce layout thrashing.
242+ const stickyOffsets : number [ ] = [ ] ;
243+ const stickyCellHeights : ( number | undefined ) [ ] = [ ] ;
244+ const elementsToStick : HTMLElement [ ] [ ] = [ ] ;
246245
247- const borderedRowIndex = states . lastIndexOf ( true ) ;
248-
249- for ( let rowIndex = 0 ; rowIndex < rows . length ; rowIndex ++ ) {
250- if ( ! states [ rowIndex ] ) {
251- continue ;
252- }
253-
254- const offset = stickyOffsets [ rowIndex ] ;
255- const isBorderedRowIndex = rowIndex === borderedRowIndex ;
256- for ( const element of elementsToStick [ rowIndex ] ) {
257- this . _addStickyStyle ( element , position , offset , isBorderedRowIndex ) ;
258- }
259- }
246+ // Coalesce with other sticky row updates (top/bottom), sticky columns updates
247+ // (and potentially other changes like column resize).
248+ afterNextRender (
249+ {
250+ earlyRead : ( ) => {
251+ for ( let rowIndex = 0 , stickyOffset = 0 ; rowIndex < rows . length ; rowIndex ++ ) {
252+ if ( ! states [ rowIndex ] ) {
253+ continue ;
254+ }
255+
256+ stickyOffsets [ rowIndex ] = stickyOffset ;
257+ const row = rows [ rowIndex ] ;
258+ elementsToStick [ rowIndex ] = this . _isNativeHtmlTable
259+ ? ( Array . from ( row . children ) as HTMLElement [ ] )
260+ : [ row ] ;
261+
262+ const height = this . _retrieveElementSize ( row ) . height ;
263+ stickyOffset += height ;
264+ stickyCellHeights [ rowIndex ] = height ;
265+ }
266+ } ,
267+ write : ( ) => {
268+ const borderedRowIndex = states . lastIndexOf ( true ) ;
269+
270+ for ( let rowIndex = 0 ; rowIndex < rows . length ; rowIndex ++ ) {
271+ if ( ! states [ rowIndex ] ) {
272+ continue ;
273+ }
274+
275+ const offset = stickyOffsets [ rowIndex ] ;
276+ const isBorderedRowIndex = rowIndex === borderedRowIndex ;
277+ for ( const element of elementsToStick [ rowIndex ] ) {
278+ this . _addStickyStyle ( element , position , offset , isBorderedRowIndex ) ;
279+ }
280+ }
260281
261- if ( position === 'top' ) {
262- this . _positionListener ?. stickyHeaderRowsUpdated ( {
263- sizes : stickyCellHeights ,
264- offsets : stickyOffsets ,
265- elements : elementsToStick ,
266- } ) ;
267- } else {
268- this . _positionListener ?. stickyFooterRowsUpdated ( {
269- sizes : stickyCellHeights ,
270- offsets : stickyOffsets ,
271- elements : elementsToStick ,
272- } ) ;
273- }
274- } ) ;
282+ if ( position === 'top' ) {
283+ this . _positionListener ?. stickyHeaderRowsUpdated ( {
284+ sizes : stickyCellHeights ,
285+ offsets : stickyOffsets ,
286+ elements : elementsToStick ,
287+ } ) ;
288+ } else {
289+ this . _positionListener ?. stickyFooterRowsUpdated ( {
290+ sizes : stickyCellHeights ,
291+ offsets : stickyOffsets ,
292+ elements : elementsToStick ,
293+ } ) ;
294+ }
295+ } ,
296+ } ,
297+ { injector : this . _tableInjector } ,
298+ ) ;
275299 }
276300
277301 /**
@@ -286,17 +310,30 @@ export class StickyStyler {
286310 }
287311
288312 // Coalesce with other sticky updates (and potentially other changes like column resize).
289- this . _coalescedStyleScheduler . schedule ( ( ) => {
290- const tfoot = tableElement . querySelector ( 'tfoot' ) ! ;
291-
292- if ( tfoot ) {
293- if ( stickyStates . some ( state => ! state ) ) {
294- this . _removeStickyStyle ( tfoot , [ 'bottom' ] ) ;
295- } else {
296- this . _addStickyStyle ( tfoot , 'bottom' , 0 , false ) ;
297- }
298- }
299- } ) ;
313+ afterNextRender (
314+ {
315+ write : ( ) => {
316+ const tfoot = tableElement . querySelector ( 'tfoot' ) ! ;
317+
318+ if ( tfoot ) {
319+ if ( stickyStates . some ( state => ! state ) ) {
320+ this . _removeStickyStyle ( tfoot , [ 'bottom' ] ) ;
321+ } else {
322+ this . _addStickyStyle ( tfoot , 'bottom' , 0 , false ) ;
323+ }
324+ }
325+ } ,
326+ } ,
327+ { injector : this . _tableInjector } ,
328+ ) ;
329+ }
330+
331+ destroy ( ) {
332+ if ( this . _stickyColumnsReplayTimeout ) {
333+ clearTimeout ( this . _stickyColumnsReplayTimeout ) ;
334+ }
335+
336+ this . _destroyed = true ;
300337 }
301338
302339 /**
@@ -516,6 +553,10 @@ export class StickyStyler {
516553 }
517554
518555 this . _stickyColumnsReplayTimeout = setTimeout ( ( ) => {
556+ if ( this . _destroyed ) {
557+ return ;
558+ }
559+
519560 for ( const update of this . _updatedStickyColumnsParamsToReplay ) {
520561 this . updateStickyColumns (
521562 update . rows ,
0 commit comments