@tanstack/angular-table 9.0.0-alpha.46 → 9.0.0-alpha.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,425 @@
1
+ ---
2
+ name: angular/table-state
3
+ description: >
4
+ TanStack Table v9 state ownership in Angular: signal-backed atoms via `angularReactivity`,
5
+ the `injectTable(() => ({...}))` lazy initializer pattern, reading `table.atoms.<slice>.get()`
6
+ inside templates / `computed(...)` / `effect(...)`, `shallow` for object slices, controlled state
7
+ with Angular signals + `state` + `on[State]Change`, and when to reach for external TanStack Store
8
+ atoms instead. Required reading before any other Angular Table v9 skill.
9
+ type: framework
10
+ library: tanstack-table
11
+ framework: angular
12
+ library_version: '9.0.0-alpha.47'
13
+ requires:
14
+ - state-management
15
+ - setup
16
+ sources:
17
+ - TanStack/table:docs/framework/angular/angular-table.md
18
+ - TanStack/table:docs/framework/angular/guide/table-state.md
19
+ - TanStack/table:docs/framework/angular/guide/migrating.md
20
+ - TanStack/table:packages/angular-table/src/injectTable.ts
21
+ - TanStack/table:packages/angular-table/src/reactivity.ts
22
+ - TanStack/table:packages/angular-table/src/lazySignalInitializer.ts
23
+ - TanStack/table:examples/angular/basic-inject-table/
24
+ - TanStack/table:examples/angular/row-selection-signal/
25
+ ---
26
+
27
+ # Angular Table State (v9)
28
+
29
+ > **TanStack Table is a state-management coordinator.** v9 rebuilt that coordinator
30
+ > on top of TanStack Store (`alien-signals`). In Angular, the adapter bridges those
31
+ > atoms to native Angular signals, so reading `table.atoms.<slice>.get()` from a
32
+ > template, `computed(...)`, or `effect(...)` participates in Angular reactivity.
33
+ >
34
+ > This is the prerequisite for every other Angular Table skill. Don't skip it.
35
+
36
+ ---
37
+
38
+ ## 1. Prerequisites — `_features` and `_rowModels` decide what state exists
39
+
40
+ In v9, **a state slice only exists if its feature is registered in `_features`**.
41
+ This is the #1 v9-specific gotcha and the root cause of many "missing API"
42
+ TypeScript errors.
43
+
44
+ ```ts
45
+ import {
46
+ injectTable,
47
+ tableFeatures,
48
+ rowPaginationFeature,
49
+ rowSortingFeature,
50
+ createPaginatedRowModel,
51
+ createSortedRowModel,
52
+ sortFns,
53
+ } from '@tanstack/angular-table'
54
+
55
+ // Declare features OUTSIDE the initializer (see §2 below)
56
+ const _features = tableFeatures({
57
+ rowPaginationFeature,
58
+ rowSortingFeature,
59
+ })
60
+
61
+ readonly table = injectTable(() => ({
62
+ _features,
63
+ _rowModels: {
64
+ paginatedRowModel: createPaginatedRowModel(),
65
+ sortedRowModel: createSortedRowModel(sortFns),
66
+ },
67
+ columns,
68
+ data: this.data(),
69
+ }))
70
+
71
+ this.table.atoms.pagination.get() // ✅
72
+ this.table.atoms.sorting.get() // ✅
73
+ // this.table.atoms.rowSelection // ❌ TS error — rowSelectionFeature not registered
74
+ ```
75
+
76
+ If you see `Property 'atoms.rowSelection' does not exist` or
77
+ `table.toggleRowSelected is not a function`, **add the feature to `_features`** —
78
+ don't reach for `@ts-ignore`, don't reimplement the API, don't switch to
79
+ `stockFeatures` until you understand which features you actually need.
80
+
81
+ `tableFeatures({})` (empty) is valid — you get the core row model only.
82
+
83
+ ---
84
+
85
+ ## 2. The `injectTable` lazy-initializer model
86
+
87
+ `injectTable` is the v9 entrypoint (replacing v8's `createAngularTable`). It must
88
+ run inside an Angular injection context (a component constructor / class field).
89
+
90
+ ```ts
91
+ readonly table = injectTable(() => ({
92
+ _features,
93
+ _rowModels: {},
94
+ columns,
95
+ data: this.data(),
96
+ }))
97
+ ```
98
+
99
+ ### The initializer is a `computed`-like function
100
+
101
+ The initializer runs **whenever any Angular signal read inside it changes**. The
102
+ adapter then calls `table.setOptions({ ...previous, ...newOptions })` to sync.
103
+ That means:
104
+
105
+ - **Reactive values that should re-sync the table** (`this.data()`, controlled
106
+ state signals) go _inside_ the initializer.
107
+ - **Stable references** (`columns`, `_features`, `_rowModels`, feature-fn maps)
108
+ go _outside_ — or you'll recreate the column model on every data update.
109
+
110
+ ```ts
111
+ // ❌ WRONG — columns + _features recreated on every data change
112
+ readonly table = injectTable(() => ({
113
+ _features: tableFeatures({ rowSortingFeature }), // new reference each run
114
+ _rowModels: { sortedRowModel: createSortedRowModel(sortFns) }, // ditto
115
+ columns: [/* … */], // ditto
116
+ data: this.data(),
117
+ }))
118
+
119
+ // ✅ Stable references outside, signal reads inside
120
+ const _features = tableFeatures({ rowSortingFeature })
121
+ const columns: Array<ColumnDef<typeof _features, Person>> = [/* … */]
122
+
123
+ readonly table = injectTable(() => ({
124
+ _features,
125
+ _rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
126
+ columns,
127
+ data: this.data(), // ← only the signal read should be inside
128
+ }))
129
+ ```
130
+
131
+ ### The returned table is signal-reactive too
132
+
133
+ The `table` returned by `injectTable` exposes APIs that read signal-backed atoms
134
+ internally, so calling `table.getRowModel()`, `table.getSelectedRowModel()`,
135
+ `table.atoms.pagination.get()`, etc. inside templates / `computed` / `effect`
136
+ _just works_ — no manual subscriptions.
137
+
138
+ ---
139
+
140
+ ## 3. The three state surfaces
141
+
142
+ A table instance has three ways to look at its state:
143
+
144
+ | Surface | Shape | Use when |
145
+ | ------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------- |
146
+ | `table.baseAtoms.<slice>` | writable TanStack Store atom (always exists for registered slices) | low-level direct write; rare |
147
+ | `table.atoms.<slice>` | **readonly** derived atom per registered feature; backed by Angular `computed` | reading current value or driving reactivity |
148
+ | `table.store.state` | flat snapshot object of every registered slice; backed by Angular `computed` | reading multiple slices at once, devtools |
149
+
150
+ All three are signal-backed in Angular. Reading any of them inside a template,
151
+ `computed(...)`, or `effect(...)` registers an Angular dependency.
152
+
153
+ ```ts
154
+ // Read current value (anywhere)
155
+ const pagination = this.table.atoms.pagination.get()
156
+
157
+ // Same value, flat shape
158
+ const pagination2 = this.table.store.state.pagination
159
+
160
+ // Reactive derivation with custom equality
161
+ import { computed } from '@angular/core'
162
+ import { shallow } from '@tanstack/angular-table'
163
+
164
+ readonly pageIndex = computed(
165
+ () => this.table.atoms.pagination.get().pageIndex,
166
+ )
167
+
168
+ readonly pagination = computed(
169
+ () => this.table.atoms.pagination.get(),
170
+ { equal: shallow }, // structural equality — skip downstream work on no-op updates
171
+ )
172
+ ```
173
+
174
+ ### When do I need `computed(...)`?
175
+
176
+ You **don't need `computed`** just to make an atom reactive. The atom is already
177
+ signal-backed. Use `computed(...)` only when you want:
178
+
179
+ 1. **Derivation** — `computed(() => this.table.atoms.pagination.get().pageIndex)`
180
+ 2. **Custom equality** — `{ equal: shallow }` on object/array slices, so
181
+ downstream `effect`s skip no-op updates when the slice is recreated with the
182
+ same values.
183
+ 3. **Caching of an expensive transformation** that reads from multiple atoms.
184
+
185
+ For plain reads in a template, `{{ table.atoms.pagination.get().pageIndex }}`
186
+ is fine.
187
+
188
+ ---
189
+
190
+ ## 4. Setting state — use feature APIs, not direct writes
191
+
192
+ TanStack Table exposes a method for nearly every state transition. **Use those
193
+ methods.** Don't reimplement what's already in the public API — that's the #1
194
+ tell of AI-generated table code.
195
+
196
+ ```ts
197
+ // ✅ Use the API
198
+ this.table.setPageIndex(0)
199
+ this.table.nextPage()
200
+ this.table.setPageSize(25)
201
+ this.table.setSorting([{ id: 'age', desc: true }])
202
+ this.table.setColumnFilters([{ id: 'status', value: 'active' }])
203
+ this.table.toggleAllRowsSelected(true)
204
+ this.table.resetSorting()
205
+ this.table.resetPagination()
206
+ this.table.resetPagination(true) // reset to feature default, not initialState
207
+ row.toggleSelected()
208
+ column.toggleVisibility()
209
+ column.toggleSorting()
210
+
211
+ // ❌ Don't write to atoms directly unless you really have to
212
+ this.table.baseAtoms.pagination.set((old) => ({ ...old, pageIndex: 0 }))
213
+ ```
214
+
215
+ Direct `baseAtoms` writes bypass `on[State]Change` handlers and won't update
216
+ externally owned state — if you've controlled the slice with an Angular signal,
217
+ you must update the signal, not the base atom.
218
+
219
+ ---
220
+
221
+ ## 5. Setting starting values — `initialState`
222
+
223
+ `initialState` is the single right place to seed registered slices. It is also
224
+ the value that reset APIs reset to.
225
+
226
+ ```ts
227
+ readonly table = injectTable(() => ({
228
+ _features,
229
+ _rowModels: { /* … */ },
230
+ columns,
231
+ data: this.data(),
232
+ initialState: {
233
+ sorting: [{ id: 'age', desc: true }],
234
+ pagination: { pageIndex: 0, pageSize: 25 },
235
+ },
236
+ }))
237
+
238
+ // Later
239
+ this.table.resetSorting() // → initialState.sorting
240
+ this.table.resetSorting(true) // → feature default ([])
241
+ ```
242
+
243
+ `initialState` only applies to slices whose feature is registered. Mutating
244
+ `initialState` after construction does **not** re-seed state — use it for
245
+ starting values only.
246
+
247
+ ---
248
+
249
+ ## 6. Controlled state — the recommended Angular pattern
250
+
251
+ Most Angular Table apps that need cross-component access to a state slice use
252
+ **Angular signals + `state` + `on[State]Change`**. This keeps ownership in
253
+ Angular's signal model while `injectTable` keeps the table in sync.
254
+
255
+ ```ts
256
+ import { signal } from '@angular/core'
257
+ import {
258
+ injectTable,
259
+ rowPaginationFeature,
260
+ rowSortingFeature,
261
+ tableFeatures,
262
+ type PaginationState,
263
+ type SortingState,
264
+ } from '@tanstack/angular-table'
265
+
266
+ const _features = tableFeatures({ rowPaginationFeature, rowSortingFeature })
267
+
268
+ export class Component {
269
+ readonly data = signal<Array<Person>>([])
270
+ readonly sorting = signal<SortingState>([])
271
+ readonly pagination = signal<PaginationState>({ pageIndex: 0, pageSize: 10 })
272
+
273
+ readonly table = injectTable(() => ({
274
+ _features,
275
+ _rowModels: {
276
+ /* … */
277
+ },
278
+ columns,
279
+ data: this.data(),
280
+ state: {
281
+ sorting: this.sorting(),
282
+ pagination: this.pagination(),
283
+ },
284
+ onSortingChange: (updater) => {
285
+ updater instanceof Function
286
+ ? this.sorting.update(updater)
287
+ : this.sorting.set(updater)
288
+ },
289
+ onPaginationChange: (updater) => {
290
+ updater instanceof Function
291
+ ? this.pagination.update(updater)
292
+ : this.pagination.set(updater)
293
+ },
294
+ }))
295
+ }
296
+ ```
297
+
298
+ ### `on[State]Change` rules
299
+
300
+ - **Always pass an updater-or-value handler.** TanStack Table calls
301
+ `on[State]Change(updaterOrValue)` where `updaterOrValue` is either a new value
302
+ or `(old) => new` — check with `instanceof Function` / `typeof === 'function'`.
303
+ - **Pair `on[State]Change` with `state.<slice>`.** Providing
304
+ `onPaginationChange` without `state.pagination` will result in your callback
305
+ firing but the table reading its own internal atom — confusing.
306
+ - **The v8 `onStateChange` (single global handler) is gone in v9.** Slices are
307
+ controlled individually.
308
+
309
+ ### Don't double-own a slice
310
+
311
+ For any given slice, exactly one of these should be the source of truth:
312
+
313
+ - `initialState.<slice>` (uncontrolled, internal)
314
+ - `state.<slice>` + `on[State]Change` (controlled by Angular signal)
315
+ - `atoms.<slice>` (controlled by external TanStack Store atom — see §7)
316
+
317
+ If you supply both `state.x` and `atoms.x`, the external atom wins silently. If
318
+ you supply both `initialState.x` and `state.x`, `state.x` wins. Pick one.
319
+
320
+ ---
321
+
322
+ ## 7. Beyond signals: external atoms, state types, app-wide hooks
323
+
324
+ For most Angular apps, **signals + `state` + `on[State]Change` (§6) is the
325
+ right ownership model.** When you need more, see
326
+ [`references/external-atoms-and-app-hook.md`](references/external-atoms-and-app-hook.md):
327
+
328
+ - **External TanStack Store atoms** — `atoms: { pagination: paginationAtom }`
329
+ for slices owned by `@tanstack/store` / `@tanstack/angular-store`, when
330
+ multiple non-table parts of the app share the slice.
331
+ - **State type imports** — `PaginationState`, `SortingState`,
332
+ `RowSelectionState`, `TableState<typeof _features>`, etc.
333
+ - **`createTableHook(...)`** — app-wide `injectAppTable` /
334
+ `createAppColumnHelper` that pre-bind `_features` and `_rowModels`. Also
335
+ exposes `tableComponents` / `cellComponents` / `headerComponents` registries
336
+ (covered in `angular-rendering-directives`).
337
+
338
+ ---
339
+
340
+ ## Failure modes
341
+
342
+ ### 1. (CRITICAL) Hallucinating v8 `createAngularTable` / pre-v9 APIs
343
+
344
+ ```ts
345
+ // ❌ v8 — does not exist in v9
346
+ import { createAngularTable, getCoreRowModel } from '@tanstack/angular-table'
347
+ const table = createAngularTable(() => ({
348
+ columns,
349
+ data: data(),
350
+ getCoreRowModel: getCoreRowModel(),
351
+ }))
352
+
353
+ // ✅ v9
354
+ import { injectTable, tableFeatures } from '@tanstack/angular-table'
355
+ const _features = tableFeatures({})
356
+ const table = injectTable(() => ({
357
+ _features,
358
+ _rowModels: {},
359
+ columns,
360
+ data: data(),
361
+ }))
362
+ ```
363
+
364
+ Also retired: `getFilteredRowModel`, `getSortedRowModel`, `getPaginationRowModel`
365
+ as top-level options → migrated to `_rowModels: { filteredRowModel: ..., sortedRowModel: ..., paginatedRowModel: ... }`
366
+ with explicit `*Fns` parameters.
367
+
368
+ ### 2. (CRITICAL) Missing API because feature not in `_features`
369
+
370
+ `table.atoms.rowSelection`, `table.toggleAllRowsSelected`,
371
+ `row.getCanSelect`, `column.getCanSort` etc. are **only** present when the
372
+ matching feature is in `_features`. The fix is to add the feature, not to
373
+ patch around it.
374
+
375
+ ### 3. (CRITICAL) Reimplementing built-in state transitions
376
+
377
+ ```ts
378
+ // ❌ DON'T
379
+ this.pagination.update((p) => ({ ...p, pageIndex: p.pageIndex + 1 }))
380
+
381
+ // ✅
382
+ this.table.nextPage()
383
+ ```
384
+
385
+ Same for `setPageIndex`, `setPageSize`, `setSorting`, `toggleSorting`,
386
+ `setColumnFilters`, `setGlobalFilter`, `toggleSelected`, `toggleAllRowsSelected`,
387
+ `setColumnVisibility`, `setColumnOrder`, `setExpanded`, `toggleExpanded`,
388
+ `resetSorting`, `resetPagination`, `resetRowSelection`, etc.
389
+
390
+ ### 4. (HIGH) Expensive values declared **inside** the `injectTable` initializer
391
+
392
+ Because the initializer re-runs when any reactive read inside it changes,
393
+ declaring `columns`, `_features`, `_rowModels`, or feature-fn maps inside the
394
+ function causes them to be recreated and re-applied on every data update.
395
+ Move them outside the class or to stable class fields.
396
+
397
+ ### 5. (HIGH) Forgetting that the initializer re-runs
398
+
399
+ If you `console.log` inside the `injectTable` initializer, you'll see it fire
400
+ multiple times during the component lifetime — that's correct. The adapter
401
+ handles the diff and calls `table.setOptions`. Don't kick off side-effects from
402
+ inside the initializer; put them in an `effect(...)` reading the relevant
403
+ atoms.
404
+
405
+ Lower-severity failure modes (MEDIUM/LOW: `state.x` vs `atoms.x` conflict,
406
+ updater-fn handling in `on[State]Change`, in-place mutation of state values,
407
+ premature `computed` wrapping) →
408
+ [`references/external-atoms-and-app-hook.md`](references/external-atoms-and-app-hook.md#lower-severity-failure-modes-mediumlow).
409
+
410
+ ---
411
+
412
+ ## References
413
+
414
+ - [External TanStack Store atoms, state types, `createTableHook` setup, and MEDIUM failure modes](references/external-atoms-and-app-hook.md)
415
+
416
+ ---
417
+
418
+ ## See also
419
+
420
+ - `tanstack-table/angular/getting-started` — end-to-end first table
421
+ - `tanstack-table/angular/angular-rendering-directives` — `*flexRender*`, DI tokens, `flexRenderComponent`
422
+ - `tanstack-table/angular/migrate-v8-to-v9` — v8 → v9 mechanical mapping
423
+ - `tanstack-table/angular/compose-with-tanstack-store` — external atoms in depth
424
+ - `tanstack-table/angular/client-to-server` — controlled state for server-driven tables
425
+ - `tanstack-table/core/state-management` — framework-agnostic atom model
@@ -0,0 +1,152 @@
1
+ # External Atoms & `createTableHook` — Full Reference
2
+
3
+ Extended ownership options beyond signals + `state` + `on[State]Change`.
4
+
5
+ ---
6
+
7
+ ## External TanStack Store atoms — alternative ownership
8
+
9
+ The core `atoms` table option is re-exported from Angular Table. Use it when:
10
+
11
+ - You already have a `Store` / `Atom` from `@tanstack/store` or
12
+ `@tanstack/angular-store` that should drive a table slice.
13
+ - Multiple non-table parts of the app need to read/write the slice through the
14
+ same atom (e.g. URL sync, persistence, multi-table coordination).
15
+
16
+ **For most Angular apps, prefer signals + `state` first.** Atoms are the
17
+ cross-app sharing alternative, not the default. See
18
+ `compose-with-tanstack-store` for the full pattern.
19
+
20
+ ```ts
21
+ import { Store } from '@tanstack/store'
22
+
23
+ const paginationAtom = new Store<PaginationState>({ pageIndex: 0, pageSize: 10 })
24
+
25
+ readonly table = injectTable(() => ({
26
+ _features,
27
+ _rowModels: { /* … */ },
28
+ columns,
29
+ data: this.data(),
30
+ atoms: {
31
+ pagination: paginationAtom, // external atom owns the slice
32
+ },
33
+ }))
34
+ ```
35
+
36
+ When `atoms.pagination` is registered, `table.atoms.pagination.get()` reads from
37
+ the external atom (not the internal base atom). `table.setPagination(...)`
38
+ writes through the external atom's setter.
39
+
40
+ ---
41
+
42
+ ## State types
43
+
44
+ Import slice types directly from `@tanstack/angular-table`:
45
+
46
+ ```ts
47
+ import type {
48
+ PaginationState,
49
+ SortingState,
50
+ RowSelectionState,
51
+ ColumnFiltersState,
52
+ GlobalFilterTableState,
53
+ ColumnVisibilityState,
54
+ ColumnOrderState,
55
+ ColumnPinningState,
56
+ ColumnSizingState,
57
+ ColumnResizingState,
58
+ ExpandedState,
59
+ GroupingState,
60
+ TableState,
61
+ } from '@tanstack/angular-table'
62
+
63
+ // TableState<typeof _features> is inferred from registered features
64
+ type MyTableState = TableState<typeof _features>
65
+ ```
66
+
67
+ ---
68
+
69
+ ## `createTableHook` — app-wide table infrastructure
70
+
71
+ When multiple tables in an app share the same `_features`, `_rowModels`, and
72
+ component conventions, factor them into a `createTableHook(...)` call once and
73
+ import the resulting `injectAppTable` / `createAppColumnHelper`.
74
+
75
+ ```ts
76
+ // app-table.ts
77
+ import {
78
+ createTableHook,
79
+ tableFeatures,
80
+ rowSortingFeature,
81
+ rowPaginationFeature,
82
+ createSortedRowModel,
83
+ createPaginatedRowModel,
84
+ sortFns,
85
+ } from '@tanstack/angular-table'
86
+
87
+ export const {
88
+ injectAppTable,
89
+ createAppColumnHelper,
90
+ injectTableContext,
91
+ injectTableCellContext,
92
+ injectTableHeaderContext,
93
+ } = createTableHook({
94
+ _features: tableFeatures({ rowSortingFeature, rowPaginationFeature }),
95
+ _rowModels: {
96
+ sortedRowModel: createSortedRowModel(sortFns),
97
+ paginatedRowModel: createPaginatedRowModel(),
98
+ },
99
+ getRowId: (row) => row.id,
100
+ })
101
+ ```
102
+
103
+ Then in components:
104
+
105
+ ```ts
106
+ import { injectAppTable, createAppColumnHelper } from './app-table'
107
+
108
+ const columnHelper = createAppColumnHelper<Person>() // only TData generic needed
109
+
110
+ readonly table = injectAppTable(() => ({
111
+ columns,
112
+ data: this.data(),
113
+ })) // _features & _rowModels inherited
114
+ ```
115
+
116
+ `createTableHook` also lets you register `tableComponents` / `cellComponents` /
117
+ `headerComponents` registries — see `angular-rendering-directives` for that.
118
+
119
+ ---
120
+
121
+ ## Lower-severity failure modes (MEDIUM/LOW)
122
+
123
+ ### Using `state.x` and `atoms.x` together for the same slice
124
+
125
+ External atom silently wins. If you intended Angular-signal ownership, remove
126
+ `atoms.x` (or vice versa). Pick exactly one source of truth per slice.
127
+
128
+ ### Forgetting the updater function form in `on[State]Change`
129
+
130
+ ```ts
131
+ // ❌ Crashes when TanStack Table passes a function
132
+ onSortingChange: (value) => this.sorting.set(value)
133
+
134
+ // ✅
135
+ onSortingChange: (updater) => {
136
+ updater instanceof Function
137
+ ? this.sorting.update(updater)
138
+ : this.sorting.set(updater)
139
+ }
140
+ ```
141
+
142
+ ### Mutating `state` slice values in place
143
+
144
+ `state: { sorting: this.sorting() }` snapshots the current value. Mutating the
145
+ underlying signal value with `.push()` won't trigger re-evaluation. Always
146
+ produce a new reference: `this.sorting.update(s => [...s, newSort])`.
147
+
148
+ ### Premature `computed` selector wrapping for a single read
149
+
150
+ For a single atom read in a template (`{{ table.atoms.pagination.get().pageIndex }}`),
151
+ a wrapper `computed` adds no value. Reach for `computed` only for derivation,
152
+ shared selectors, or `{ equal: shallow }`.
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  Injector,
3
+ NgZone,
3
4
  assertInInjectionContext,
4
5
  effect,
5
6
  inject,
@@ -96,34 +97,37 @@ export function injectTable<
96
97
  ): AngularTable<TFeatures, TData> {
97
98
  assertInInjectionContext(injectTable)
98
99
  const injector = inject(Injector)
100
+ const ngZone = inject(NgZone)
99
101
 
100
- return lazyInit(() => {
101
- const table = constructTable({
102
- ...options(),
103
- _features: {
104
- coreReativityFeature: angularReactivity(injector),
105
- ...options()._features,
106
- },
107
- })
102
+ return ngZone.runOutsideAngular(() =>
103
+ lazyInit(() => {
104
+ const table = constructTable({
105
+ ...options(),
106
+ _features: {
107
+ coreReativityFeature: angularReactivity(injector),
108
+ ...options()._features,
109
+ },
110
+ })
108
111
 
109
- let isMount = true
110
- effect(
111
- () => {
112
- const newOptions = options()
113
- if (isMount) {
114
- isMount = false
115
- return
116
- }
117
- untracked(() =>
118
- table.setOptions((previous) => ({
119
- ...previous,
120
- ...newOptions,
121
- })),
122
- )
123
- },
124
- { injector, debugName: 'tableOptionsUpdate' },
125
- )
112
+ let isMount = true
113
+ effect(
114
+ () => {
115
+ const newOptions = options()
116
+ if (isMount) {
117
+ isMount = false
118
+ return
119
+ }
120
+ untracked(() =>
121
+ table.setOptions((previous) => ({
122
+ ...previous,
123
+ ...newOptions,
124
+ })),
125
+ )
126
+ },
127
+ { injector, debugName: 'tableOptionsUpdate' },
128
+ )
126
129
 
127
- return table
128
- })
130
+ return table
131
+ }),
132
+ )
129
133
  }
package/src/reactivity.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { computed, signal, untracked } from '@angular/core'
1
+ import { NgZone, computed, signal, untracked } from '@angular/core'
2
2
  import { toObservable } from '@angular/core/rxjs-interop'
3
3
  import type { Atom, Observer, ReadonlyAtom } from '@tanstack/angular-store'
4
4
  import type {
@@ -49,8 +49,10 @@ function signalToWritableAtom<T>(
49
49
  * and `effect` calls.
50
50
  */
51
51
  export function angularReactivity(injector: Injector): TableReactivityBindings {
52
+ const ngZone = injector.get(NgZone)
52
53
  return {
53
54
  createOptionsStore: true,
55
+ schedule: (fn) => ngZone.runOutsideAngular(() => queueMicrotask(fn)),
54
56
  createReadonlyAtom: <T>(fn: () => T, options?: TableAtomOptions<T>) => {
55
57
  const signal = computed(() => fn(), {
56
58
  equal: options?.compare,