Skip to content

Commit eef3111

Browse files
committed
fix(angular): flush dom with app ref tick on update
1 parent 79bb574 commit eef3111

File tree

1 file changed

+49
-9
lines changed

1 file changed

+49
-9
lines changed

packages/angular-virtual/src/index.ts

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import {
2+
ApplicationRef,
3+
DestroyRef,
24
afterRenderEffect,
35
computed,
6+
inject,
47
linkedSignal,
58
untracked,
69
} from '@angular/core'
@@ -21,25 +24,62 @@ import type { AngularVirtualizer } from './types'
2124
export * from '@tanstack/virtual-core'
2225
export * from './types'
2326

24-
function createVirtualizerBase<
27+
export type AngularVirtualizerOptions<
28+
TScrollElement extends Element | Window,
29+
TItemElement extends Element,
30+
> = VirtualizerOptions<TScrollElement, TItemElement> & {
31+
/**
32+
* Whether to flush the DOM using `ApplicationRef.tick()`
33+
* @default true
34+
* */
35+
useApplicationRefTick?: boolean
36+
}
37+
38+
// Flush CD after virtual-core updates so template bindings hit the DOM
39+
// before the next frame's scroll reconciliation reads `scrollHeight`.
40+
function injectScheduleDomFlushViaAppRefTick() {
41+
const appRef = inject(ApplicationRef)
42+
const destroyRef = inject(DestroyRef)
43+
let hostDestroyed = false
44+
destroyRef.onDestroy(() => {
45+
hostDestroyed = true
46+
})
47+
let domFlushQueued = false
48+
49+
return () => {
50+
if (domFlushQueued) return
51+
domFlushQueued = true
52+
queueMicrotask(() => {
53+
domFlushQueued = false
54+
if (hostDestroyed) return
55+
appRef.tick()
56+
})
57+
}
58+
}
59+
60+
function injectVirtualizerBase<
2561
TScrollElement extends Element | Window,
2662
TItemElement extends Element,
2763
>(
28-
options: () => VirtualizerOptions<TScrollElement, TItemElement>,
64+
options: () => AngularVirtualizerOptions<TScrollElement, TItemElement>,
2965
) {
66+
const scheduleDomFlush = injectScheduleDomFlushViaAppRefTick()
67+
3068
const resolvedOptions = computed<VirtualizerOptions<TScrollElement, TItemElement>>(() => {
31-
const _options = options()
69+
const { useApplicationRefTick = true, ..._options } = options()
3270
return {
3371
..._options,
3472
onChange: (instance, sync) => {
35-
// Update the main signal to trigger a re-render
3673
reactiveVirtualizer.set(instance)
74+
if (useApplicationRefTick) {
75+
scheduleDomFlush()
76+
}
3777
_options.onChange?.(instance, sync)
3878
},
3979
}
4080
})
4181

42-
const lazyVirtualizer = computed(() => new Virtualizer(untracked(options)))
82+
const lazyVirtualizer = computed(() => new Virtualizer(untracked(resolvedOptions)))
4383

4484
const reactiveVirtualizer = linkedSignal(() => {
4585
const virtualizer = lazyVirtualizer()
@@ -104,13 +144,13 @@ export function injectVirtualizer<
104144
TItemElement extends Element,
105145
>(
106146
options: () => PartialKeys<
107-
Omit<VirtualizerOptions<TScrollElement, TItemElement>, 'getScrollElement'>,
147+
Omit<AngularVirtualizerOptions<TScrollElement, TItemElement>, 'getScrollElement'>,
108148
'observeElementRect' | 'observeElementOffset' | 'scrollToFn'
109149
> & {
110150
scrollElement: ElementRef<TScrollElement> | TScrollElement | undefined
111151
},
112152
): AngularVirtualizer<TScrollElement, TItemElement> {
113-
return createVirtualizerBase<TScrollElement, TItemElement>(() => {
153+
return injectVirtualizerBase<TScrollElement, TItemElement>(() => {
114154
const _options = options()
115155
return {
116156
observeElementRect: observeElementRect,
@@ -137,14 +177,14 @@ function isElementRef<T extends Element>(
137177

138178
export function injectWindowVirtualizer<TItemElement extends Element>(
139179
options: () => PartialKeys<
140-
VirtualizerOptions<Window, TItemElement>,
180+
AngularVirtualizerOptions<Window, TItemElement>,
141181
| 'getScrollElement'
142182
| 'observeElementRect'
143183
| 'observeElementOffset'
144184
| 'scrollToFn'
145185
>,
146186
): AngularVirtualizer<Window, TItemElement> {
147-
return createVirtualizerBase<Window, TItemElement>(() => ({
187+
return injectVirtualizerBase<Window, TItemElement>(() => ({
148188
getScrollElement: () => (typeof document !== 'undefined' ? window : null),
149189
observeElementRect: observeWindowRect,
150190
observeElementOffset: observeWindowOffset,

0 commit comments

Comments
 (0)