Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/early-tips-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/angular-table': patch
---

Ensure options updates are not missed during first mount
65 changes: 36 additions & 29 deletions packages/angular-table/src/injectTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
Injector,
NgZone,
assertInInjectionContext,
computed,
effect,
inject,
untracked,
Expand Down Expand Up @@ -91,47 +92,53 @@ export function injectTable<
TFeatures extends TableFeatures,
TData extends RowData,
>(
options: () => TableOptions<TFeatures, TData>,
_options: () => TableOptions<TFeatures, TData>,
): AngularTable<TFeatures, TData> {
assertInInjectionContext(injectTable)
const injector = inject(Injector)
const ngZone = inject(NgZone)
const options = computed(() => _options())
const coreReactivityFeature = angularReactivity(injector)

let table: Table<TFeatures, TData>
injector.get(DestroyRef).onDestroy(() => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (table) {
table._reactivity.unmount?.()
}
})

let previousOptions: TableOptions<TFeatures, TData>
effect(
() => {
const currentOptions = options()
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!table) {
return
}
if (previousOptions === currentOptions) {
return
}
untracked(() =>
table.setOptions((previous) => ({
...previous,
...currentOptions,
})),
)
previousOptions = currentOptions
},
{ injector, debugName: 'tableOptionsUpdate' },
)

return ngZone.runOutsideAngular(() =>
lazyInit(() => {
// Explicit type arguments skip generic inference from the spread object
// (a type-check hot spot); the spread only adds the angular reactivity
// binding to `features`.
const table = constructTable<TFeatures, TData>({
table = constructTable<TFeatures, TData>({
...options(),
features: {
coreReactivityFeature: angularReactivity(injector),
coreReactivityFeature,
...options().features,
},
})

injector.get(DestroyRef).onDestroy(() => {
table._reactivity.unmount?.()
})

let isMount = true
effect(
() => {
const newOptions = options()
if (isMount) {
isMount = false
return
}
untracked(() =>
table.setOptions((previous) => ({
...previous,
...newOptions,
})),
)
},
{ injector, debugName: 'tableOptionsUpdate' },
)

return table
}),
)
Expand Down
2 changes: 0 additions & 2 deletions packages/angular-table/src/lazySignalInitializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ export function lazyInit<T extends object>(initializer: () => T): T {
}
}

queueMicrotask(() => initializeObject())

const table = () => {}

return new Proxy<T>(table as T, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe('angularReactivityFeature', () => {
return TestBed.runInInjectionContext(() =>
injectTable(() => ({
data: _data(),
features: { ...stockFeatures },
features: stockFeatures,
columns: columns,
getRowId: (row) => row.id,
})),
Expand Down
25 changes: 25 additions & 0 deletions packages/angular-table/tests/injectTable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,29 @@ describe('injectTable', () => {
})
})
})

// Fixes https://github.com/TanStack/table/issues/6530
test('does not drop an options update before the effect first runs', () => {
type Data = { id: string }

const initialData: Array<Data> = []
const updatedData: Array<Data> = [{ id: '1' }]
const data = signal(initialData)

const table = TestBed.runInInjectionContext(() =>
injectTable(() => ({
data: data(),
columns: [],
features: stockFeatures,
getRowId: (row) => row.id,
})),
)

expect(table.options.data).toBe(initialData)

data.set(updatedData)
TestBed.tick()

expect(table.options.data).toBe(updatedData)
})
})
4 changes: 2 additions & 2 deletions packages/angular-table/tests/lazy-init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { flushQueue, setFixtureSignalInputs } from './test-utils'
import type { WritableSignal } from '@angular/core'

describe('lazyInit', () => {
test('should init lazily in next tick when not accessing manually', async () => {
test('should not init lazily in next tick when not accessing manually', async () => {
const mockFn = vi.fn()

TestBed.runInInjectionContext(() => {
Expand All @@ -29,7 +29,7 @@ describe('lazyInit', () => {

await new Promise(setImmediate)

expect(mockFn).toHaveBeenCalled()
expect(mockFn).not.toHaveBeenCalled()
})

test('should init eagerly accessing manually', () => {
Expand Down
Loading