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' ;
14- import { _CoalescedStyleScheduler } from './coalesced-style-scheduler' ;
1515import { StickyPositioningListener } from './sticky-position-listener' ;
1616
1717export type StickyDirection = 'top' | 'bottom' | 'left' | 'right' ;
@@ -41,6 +41,7 @@ export class StickyStyler {
4141 private _stickyColumnsReplayTimeout : number | null = null ;
4242 private _cachedCellWidths : number [ ] = [ ] ;
4343 private readonly _borderCellCss : Readonly < { [ d in StickyDirection ] : string } > ;
44+ private _destroyed = false ;
4445
4546 /**
4647 * @param _isNativeHtmlTable Whether the sticky logic should be based on a table
@@ -60,10 +61,10 @@ export class StickyStyler {
6061 private _isNativeHtmlTable : boolean ,
6162 private _stickCellCss : string ,
6263 public direction : Direction ,
63- private _coalescedStyleScheduler : _CoalescedStyleScheduler ,
6464 private _isBrowser = true ,
6565 private readonly _needsPositionStickyOnElement = true ,
6666 private readonly _positionListener ?: StickyPositioningListener ,
67+ private readonly _tableInjector ?: Injector ,
6768 ) {
6869 this . _borderCellCss = {
6970 'top' : `${ _stickCellCss } -border-elem-top` ,
@@ -92,18 +93,20 @@ export class StickyStyler {
9293 continue ;
9394 }
9495
95- elementsToClear . push ( row ) ;
96- for ( let i = 0 ; i < row . children . length ; i ++ ) {
97- elementsToClear . push ( row . children [ i ] as HTMLElement ) ;
98- }
96+ elementsToClear . push ( row , ...( Array . from ( row . children ) as HTMLElement [ ] ) ) ;
9997 }
10098
10199 // 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- } ) ;
100+ afterNextRender (
101+ {
102+ write : ( ) => {
103+ for ( const element of elementsToClear ) {
104+ this . _removeStickyStyle ( element , stickyDirections ) ;
105+ }
106+ } ,
107+ } ,
108+ { injector : this . _tableInjector } ,
109+ ) ;
107110 }
108111
109112 /**
@@ -147,54 +150,67 @@ export class StickyStyler {
147150 }
148151
149152 // 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 ) ;
153+ const firstRow = rows [ 0 ] ;
154+ const numCells = firstRow . children . length ;
155+
156+ const isRtl = this . direction === 'rtl' ;
157+ const start = isRtl ? 'right' : 'left' ;
158+ const end = isRtl ? 'left' : 'right' ;
159+
160+ const lastStickyStart = stickyStartStates . lastIndexOf ( true ) ;
161+ const firstStickyEnd = stickyEndStates . indexOf ( true ) ;
162+
163+ let cellWidths : number [ ] ;
164+ let startPositions : number [ ] ;
165+ let endPositions : number [ ] ;
166+
167+ afterNextRender (
168+ {
169+ earlyRead : ( ) => {
170+ cellWidths = this . _getCellWidths ( firstRow , recalculateCellWidths ) ;
171+
172+ startPositions = this . _getStickyStartColumnPositions ( cellWidths , stickyStartStates ) ;
173+ endPositions = this . _getStickyEndColumnPositions ( cellWidths , stickyEndStates ) ;
174+ } ,
175+ write : ( ) => {
176+ for ( const row of rows ) {
177+ for ( let i = 0 ; i < numCells ; i ++ ) {
178+ const cell = row . children [ i ] as HTMLElement ;
179+ if ( stickyStartStates [ i ] ) {
180+ this . _addStickyStyle ( cell , start , startPositions [ i ] , i === lastStickyStart ) ;
181+ }
182+
183+ if ( stickyEndStates [ i ] ) {
184+ this . _addStickyStyle ( cell , end , endPositions [ i ] , i === firstStickyEnd ) ;
185+ }
186+ }
170187 }
171188
172- if ( stickyEndStates [ i ] ) {
173- this . _addStickyStyle ( cell , end , endPositions [ i ] , i === firstStickyEnd ) ;
189+ if ( this . _positionListener ) {
190+ this . _positionListener . stickyColumnsUpdated ( {
191+ sizes :
192+ lastStickyStart === - 1
193+ ? [ ]
194+ : cellWidths
195+ . slice ( 0 , lastStickyStart + 1 )
196+ . map ( ( width , index ) => ( stickyStartStates [ index ] ? width : null ) ) ,
197+ } ) ;
198+ this . _positionListener . stickyEndColumnsUpdated ( {
199+ sizes :
200+ firstStickyEnd === - 1
201+ ? [ ]
202+ : cellWidths
203+ . slice ( firstStickyEnd )
204+ . map ( ( width , index ) =>
205+ stickyEndStates [ index + firstStickyEnd ] ? width : null ,
206+ )
207+ . reverse ( ) ,
208+ } ) ;
174209 }
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- } ) ;
210+ } ,
211+ } ,
212+ { injector : this . _tableInjector } ,
213+ ) ;
198214 }
199215
200216 /**
@@ -214,64 +230,70 @@ export class StickyStyler {
214230 return ;
215231 }
216232
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- }
233+ // If positioning the rows to the bottom, reverse their order when evaluating the sticky
234+ // position such that the last row stuck will be "bottom: 0px" and so on. Note that the
235+ // sticky states need to be reversed as well.
236+ const rows = position === 'bottom' ? rowsToStick . slice ( ) . reverse ( ) : rowsToStick ;
237+ const states = position === 'bottom' ? stickyStates . slice ( ) . reverse ( ) : stickyStates ;
235238
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- }
239+ // Measure row heights all at once before adding sticky styles to reduce layout thrashing.
240+ const stickyOffsets : number [ ] = [ ] ;
241+ const stickyCellHeights : ( number | undefined ) [ ] = [ ] ;
242+ const elementsToStick : HTMLElement [ ] [ ] = [ ] ;
246243
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- }
244+ // Coalesce with other sticky row updates (top/bottom), sticky columns updates
245+ // (and potentially other changes like column resize).
246+ afterNextRender (
247+ {
248+ earlyRead : ( ) => {
249+ for ( let rowIndex = 0 , stickyOffset = 0 ; rowIndex < rows . length ; rowIndex ++ ) {
250+ if ( ! states [ rowIndex ] ) {
251+ continue ;
252+ }
253+
254+ stickyOffsets [ rowIndex ] = stickyOffset ;
255+ const row = rows [ rowIndex ] ;
256+ elementsToStick [ rowIndex ] = this . _isNativeHtmlTable
257+ ? ( Array . from ( row . children ) as HTMLElement [ ] )
258+ : [ row ] ;
259+
260+ const height = this . _retrieveElementSize ( row ) . height ;
261+ stickyOffset += height ;
262+ stickyCellHeights [ rowIndex ] = height ;
263+ }
264+ } ,
265+ write : ( ) => {
266+ const borderedRowIndex = states . lastIndexOf ( true ) ;
267+
268+ for ( let rowIndex = 0 ; rowIndex < rows . length ; rowIndex ++ ) {
269+ if ( ! states [ rowIndex ] ) {
270+ continue ;
271+ }
272+
273+ const offset = stickyOffsets [ rowIndex ] ;
274+ const isBorderedRowIndex = rowIndex === borderedRowIndex ;
275+ for ( const element of elementsToStick [ rowIndex ] ) {
276+ this . _addStickyStyle ( element , position , offset , isBorderedRowIndex ) ;
277+ }
278+ }
260279
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- } ) ;
280+ if ( position === 'top' ) {
281+ this . _positionListener ?. stickyHeaderRowsUpdated ( {
282+ sizes : stickyCellHeights ,
283+ offsets : stickyOffsets ,
284+ elements : elementsToStick ,
285+ } ) ;
286+ } else {
287+ this . _positionListener ?. stickyFooterRowsUpdated ( {
288+ sizes : stickyCellHeights ,
289+ offsets : stickyOffsets ,
290+ elements : elementsToStick ,
291+ } ) ;
292+ }
293+ } ,
294+ } ,
295+ { injector : this . _tableInjector } ,
296+ ) ;
275297 }
276298
277299 /**
@@ -286,17 +308,30 @@ export class StickyStyler {
286308 }
287309
288310 // 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- } ) ;
311+ afterNextRender (
312+ {
313+ write : ( ) => {
314+ const tfoot = tableElement . querySelector ( 'tfoot' ) ! ;
315+
316+ if ( tfoot ) {
317+ if ( stickyStates . some ( state => ! state ) ) {
318+ this . _removeStickyStyle ( tfoot , [ 'bottom' ] ) ;
319+ } else {
320+ this . _addStickyStyle ( tfoot , 'bottom' , 0 , false ) ;
321+ }
322+ }
323+ } ,
324+ } ,
325+ { injector : this . _tableInjector } ,
326+ ) ;
327+ }
328+
329+ destroy ( ) {
330+ if ( this . _stickyColumnsReplayTimeout ) {
331+ clearTimeout ( this . _stickyColumnsReplayTimeout ) ;
332+ }
333+
334+ this . _destroyed = true ;
300335 }
301336
302337 /**
@@ -516,6 +551,10 @@ export class StickyStyler {
516551 }
517552
518553 this . _stickyColumnsReplayTimeout = setTimeout ( ( ) => {
554+ if ( this . _destroyed ) {
555+ return ;
556+ }
557+
519558 for ( const update of this . _updatedStickyColumnsParamsToReplay ) {
520559 this . updateStickyColumns (
521560 update . rows ,
0 commit comments