diff --git a/.changeset/early-tips-matter.md b/.changeset/early-tips-matter.md new file mode 100644 index 0000000000..616a7482d9 --- /dev/null +++ b/.changeset/early-tips-matter.md @@ -0,0 +1,5 @@ +--- +'@tanstack/angular-table': patch +--- + +Ensure options updates are not missed during first mount diff --git a/packages/angular-table/src/injectTable.ts b/packages/angular-table/src/injectTable.ts index 3486e93f7c..538f724fa4 100644 --- a/packages/angular-table/src/injectTable.ts +++ b/packages/angular-table/src/injectTable.ts @@ -3,6 +3,7 @@ import { Injector, NgZone, assertInInjectionContext, + computed, effect, inject, untracked, @@ -91,47 +92,53 @@ export function injectTable< TFeatures extends TableFeatures, TData extends RowData, >( - options: () => TableOptions, + _options: () => TableOptions, ): AngularTable { assertInInjectionContext(injectTable) const injector = inject(Injector) const ngZone = inject(NgZone) + const options = computed(() => _options()) + const coreReactivityFeature = angularReactivity(injector) + + let table: Table + injector.get(DestroyRef).onDestroy(() => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (table) { + table._reactivity.unmount?.() + } + }) + + let previousOptions: TableOptions + 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({ + table = constructTable({ ...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 }), ) diff --git a/packages/angular-table/src/lazySignalInitializer.ts b/packages/angular-table/src/lazySignalInitializer.ts index 92f8dcc901..5ac64e766c 100644 --- a/packages/angular-table/src/lazySignalInitializer.ts +++ b/packages/angular-table/src/lazySignalInitializer.ts @@ -13,8 +13,6 @@ export function lazyInit(initializer: () => T): T { } } - queueMicrotask(() => initializeObject()) - const table = () => {} return new Proxy(table as T, { diff --git a/packages/angular-table/tests/angularReactivityFeature.test.ts b/packages/angular-table/tests/angularReactivityFeature.test.ts index 52074477f8..d8ab8f7ed5 100644 --- a/packages/angular-table/tests/angularReactivityFeature.test.ts +++ b/packages/angular-table/tests/angularReactivityFeature.test.ts @@ -28,7 +28,7 @@ describe('angularReactivityFeature', () => { return TestBed.runInInjectionContext(() => injectTable(() => ({ data: _data(), - features: { ...stockFeatures }, + features: stockFeatures, columns: columns, getRowId: (row) => row.id, })), diff --git a/packages/angular-table/tests/injectTable.test.ts b/packages/angular-table/tests/injectTable.test.ts index aee244543d..e8804b5b5b 100644 --- a/packages/angular-table/tests/injectTable.test.ts +++ b/packages/angular-table/tests/injectTable.test.ts @@ -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 = [] + const updatedData: Array = [{ 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) + }) }) diff --git a/packages/angular-table/tests/lazy-init.test.ts b/packages/angular-table/tests/lazy-init.test.ts index ae0dde5327..13485123af 100644 --- a/packages/angular-table/tests/lazy-init.test.ts +++ b/packages/angular-table/tests/lazy-init.test.ts @@ -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(() => { @@ -29,7 +29,7 @@ describe('lazyInit', () => { await new Promise(setImmediate) - expect(mockFn).toHaveBeenCalled() + expect(mockFn).not.toHaveBeenCalled() }) test('should init eagerly accessing manually', () => {