@tanstack/angular-query-experimental 5.73.3 → 5.74.1

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.
@@ -1,8 +1,14 @@
1
- import { DestroyRef, NgZone, inject, signal } from '@angular/core'
1
+ import {
2
+ DestroyRef,
3
+ Injector,
4
+ NgZone,
5
+ assertInInjectionContext,
6
+ inject,
7
+ signal,
8
+ } from '@angular/core'
2
9
  import { QueryClient, notifyManager } from '@tanstack/query-core'
3
- import { assertInjector } from './util/assert-injector/assert-injector'
4
10
  import type { MutationFilters } from '@tanstack/query-core'
5
- import type { Injector, Signal } from '@angular/core'
11
+ import type { Signal } from '@angular/core'
6
12
 
7
13
  export interface InjectIsMutatingOptions {
8
14
  /**
@@ -26,34 +32,34 @@ export function injectIsMutating(
26
32
  filters?: MutationFilters,
27
33
  options?: InjectIsMutatingOptions,
28
34
  ): Signal<number> {
29
- return assertInjector(injectIsMutating, options?.injector, () => {
30
- const destroyRef = inject(DestroyRef)
31
- const ngZone = inject(NgZone)
32
- const queryClient = inject(QueryClient)
35
+ !options?.injector && assertInInjectionContext(injectIsMutating)
36
+ const injector = options?.injector ?? inject(Injector)
37
+ const destroyRef = injector.get(DestroyRef)
38
+ const ngZone = injector.get(NgZone)
39
+ const queryClient = injector.get(QueryClient)
33
40
 
34
- const cache = queryClient.getMutationCache()
35
- // isMutating is the prev value initialized on mount *
36
- let isMutating = queryClient.isMutating(filters)
41
+ const cache = queryClient.getMutationCache()
42
+ // isMutating is the prev value initialized on mount *
43
+ let isMutating = queryClient.isMutating(filters)
37
44
 
38
- const result = signal(isMutating)
45
+ const result = signal(isMutating)
39
46
 
40
- const unsubscribe = ngZone.runOutsideAngular(() =>
41
- cache.subscribe(
42
- notifyManager.batchCalls(() => {
43
- const newIsMutating = queryClient.isMutating(filters)
44
- if (isMutating !== newIsMutating) {
45
- // * and update with each change
46
- isMutating = newIsMutating
47
- ngZone.run(() => {
48
- result.set(isMutating)
49
- })
50
- }
51
- }),
52
- ),
53
- )
47
+ const unsubscribe = ngZone.runOutsideAngular(() =>
48
+ cache.subscribe(
49
+ notifyManager.batchCalls(() => {
50
+ const newIsMutating = queryClient.isMutating(filters)
51
+ if (isMutating !== newIsMutating) {
52
+ // * and update with each change
53
+ isMutating = newIsMutating
54
+ ngZone.run(() => {
55
+ result.set(isMutating)
56
+ })
57
+ }
58
+ }),
59
+ ),
60
+ )
54
61
 
55
- destroyRef.onDestroy(unsubscribe)
62
+ destroyRef.onDestroy(unsubscribe)
56
63
 
57
- return result
58
- })
64
+ return result
59
65
  }
@@ -0,0 +1,50 @@
1
+ import {
2
+ InjectionToken,
3
+ Injector,
4
+ assertInInjectionContext,
5
+ computed,
6
+ inject,
7
+ } from '@angular/core'
8
+ import type { Provider, Signal } from '@angular/core'
9
+
10
+ const IS_RESTORING = new InjectionToken<Signal<boolean>>('')
11
+
12
+ /**
13
+ * The `Injector` in which to create the isRestoring signal.
14
+ *
15
+ * If this is not provided, the current injection context will be used instead (via `inject`).
16
+ */
17
+ interface InjectIsRestoringOptions {
18
+ injector?: Injector
19
+ }
20
+
21
+ /**
22
+ * Injects a signal that tracks whether a restore is currently in progress. {@link injectQuery} and friends also check this internally to avoid race conditions between the restore and mounting queries.
23
+ * @param options - Options for injectIsRestoring.
24
+ * @returns signal with boolean that indicates whether a restore is in progress.
25
+ * @public
26
+ */
27
+ export function injectIsRestoring(
28
+ options?: InjectIsRestoringOptions,
29
+ ): Signal<boolean> {
30
+ !options?.injector && assertInInjectionContext(injectIsRestoring)
31
+ const injector = options?.injector ?? inject(Injector)
32
+ return injector.get(
33
+ IS_RESTORING,
34
+ computed(() => false),
35
+ { optional: true },
36
+ )
37
+ }
38
+
39
+ /**
40
+ * Used by TanStack Query Angular persist client plugin to provide the signal that tracks the restore state
41
+ * @param isRestoring - a readonly signal that returns a boolean
42
+ * @returns Provider for the `isRestoring` signal
43
+ * @public
44
+ */
45
+ export function provideIsRestoring(isRestoring: Signal<boolean>): Provider {
46
+ return {
47
+ provide: IS_RESTORING,
48
+ useValue: isRestoring,
49
+ }
50
+ }
@@ -1,11 +1,18 @@
1
- import { DestroyRef, NgZone, computed, inject, signal } from '@angular/core'
1
+ import {
2
+ DestroyRef,
3
+ Injector,
4
+ NgZone,
5
+ assertInInjectionContext,
6
+ computed,
7
+ inject,
8
+ signal,
9
+ } from '@angular/core'
2
10
  import {
3
11
  QueryClient,
4
12
  notifyManager,
5
13
  replaceEqualDeep,
6
14
  } from '@tanstack/query-core'
7
- import { assertInjector } from './util/assert-injector/assert-injector'
8
- import type { Injector, Signal } from '@angular/core'
15
+ import type { Signal } from '@angular/core'
9
16
  import type {
10
17
  Mutation,
11
18
  MutationCache,
@@ -58,62 +65,61 @@ export function injectMutationState<TResult = MutationState>(
58
65
  injectMutationStateFn: () => MutationStateOptions<TResult> = () => ({}),
59
66
  options?: InjectMutationStateOptions,
60
67
  ): Signal<Array<TResult>> {
61
- return assertInjector(injectMutationState, options?.injector, () => {
62
- const destroyRef = inject(DestroyRef)
63
- const ngZone = inject(NgZone)
64
- const queryClient = inject(QueryClient)
65
-
66
- const mutationCache = queryClient.getMutationCache()
68
+ !options?.injector && assertInInjectionContext(injectMutationState)
69
+ const injector = options?.injector ?? inject(Injector)
70
+ const destroyRef = injector.get(DestroyRef)
71
+ const ngZone = injector.get(NgZone)
72
+ const queryClient = injector.get(QueryClient)
73
+ const mutationCache = queryClient.getMutationCache()
67
74
 
68
- /**
69
- * Computed signal that gets result from mutation cache based on passed options
70
- * First element is the result, second element is the time when the result was set
71
- */
72
- const resultFromOptionsSignal = computed(() => {
73
- return [
74
- getResult(mutationCache, injectMutationStateFn()),
75
- performance.now(),
76
- ] as const
77
- })
75
+ /**
76
+ * Computed signal that gets result from mutation cache based on passed options
77
+ * First element is the result, second element is the time when the result was set
78
+ */
79
+ const resultFromOptionsSignal = computed(() => {
80
+ return [
81
+ getResult(mutationCache, injectMutationStateFn()),
82
+ performance.now(),
83
+ ] as const
84
+ })
78
85
 
79
- /**
80
- * Signal that contains result set by subscriber
81
- * First element is the result, second element is the time when the result was set
82
- */
83
- const resultFromSubscriberSignal = signal<[Array<TResult>, number] | null>(
84
- null,
85
- )
86
+ /**
87
+ * Signal that contains result set by subscriber
88
+ * First element is the result, second element is the time when the result was set
89
+ */
90
+ const resultFromSubscriberSignal = signal<[Array<TResult>, number] | null>(
91
+ null,
92
+ )
86
93
 
87
- /**
88
- * Returns the last result by either subscriber or options
89
- */
90
- const effectiveResultSignal = computed(() => {
91
- const optionsResult = resultFromOptionsSignal()
92
- const subscriberResult = resultFromSubscriberSignal()
93
- return subscriberResult && subscriberResult[1] > optionsResult[1]
94
- ? subscriberResult[0]
95
- : optionsResult[0]
96
- })
94
+ /**
95
+ * Returns the last result by either subscriber or options
96
+ */
97
+ const effectiveResultSignal = computed(() => {
98
+ const optionsResult = resultFromOptionsSignal()
99
+ const subscriberResult = resultFromSubscriberSignal()
100
+ return subscriberResult && subscriberResult[1] > optionsResult[1]
101
+ ? subscriberResult[0]
102
+ : optionsResult[0]
103
+ })
97
104
 
98
- const unsubscribe = ngZone.runOutsideAngular(() =>
99
- mutationCache.subscribe(
100
- notifyManager.batchCalls(() => {
101
- const [lastResult] = effectiveResultSignal()
102
- const nextResult = replaceEqualDeep(
103
- lastResult,
104
- getResult(mutationCache, injectMutationStateFn()),
105
- )
106
- if (lastResult !== nextResult) {
107
- ngZone.run(() => {
108
- resultFromSubscriberSignal.set([nextResult, performance.now()])
109
- })
110
- }
111
- }),
112
- ),
113
- )
105
+ const unsubscribe = ngZone.runOutsideAngular(() =>
106
+ mutationCache.subscribe(
107
+ notifyManager.batchCalls(() => {
108
+ const [lastResult] = effectiveResultSignal()
109
+ const nextResult = replaceEqualDeep(
110
+ lastResult,
111
+ getResult(mutationCache, injectMutationStateFn()),
112
+ )
113
+ if (lastResult !== nextResult) {
114
+ ngZone.run(() => {
115
+ resultFromSubscriberSignal.set([nextResult, performance.now()])
116
+ })
117
+ }
118
+ }),
119
+ ),
120
+ )
114
121
 
115
- destroyRef.onDestroy(unsubscribe)
122
+ destroyRef.onDestroy(unsubscribe)
116
123
 
117
- return effectiveResultSignal
118
- })
124
+ return effectiveResultSignal
119
125
  }
@@ -1,6 +1,8 @@
1
1
  import {
2
2
  DestroyRef,
3
+ Injector,
3
4
  NgZone,
5
+ assertInInjectionContext,
4
6
  computed,
5
7
  effect,
6
8
  inject,
@@ -12,10 +14,8 @@ import {
12
14
  QueryClient,
13
15
  notifyManager,
14
16
  } from '@tanstack/query-core'
15
- import { assertInjector } from './util/assert-injector/assert-injector'
16
17
  import { signalProxy } from './signal-proxy'
17
18
  import { noop, shouldThrowError } from './util'
18
- import type { Injector } from '@angular/core'
19
19
  import type { DefaultError, MutationObserverResult } from '@tanstack/query-core'
20
20
  import type { CreateMutateFunction, CreateMutationResult } from './types'
21
21
  import type { CreateMutationOptions } from './mutation-options'
@@ -52,123 +52,117 @@ export function injectMutation<
52
52
  >,
53
53
  options?: InjectMutationOptions,
54
54
  ): CreateMutationResult<TData, TError, TVariables, TContext> {
55
- return assertInjector(injectMutation, options?.injector, () => {
56
- const destroyRef = inject(DestroyRef)
57
- const ngZone = inject(NgZone)
58
- const queryClient = inject(QueryClient)
59
-
60
- /**
61
- * computed() is used so signals can be inserted into the options
62
- * making it reactive. Wrapping options in a function ensures embedded expressions
63
- * are preserved and can keep being applied after signal changes
64
- */
65
- const optionsSignal = computed(injectMutationFn)
66
-
67
- const observerSignal = (() => {
68
- let instance: MutationObserver<
69
- TData,
70
- TError,
71
- TVariables,
72
- TContext
73
- > | null = null
74
-
75
- return computed(() => {
76
- return (instance ||= new MutationObserver(queryClient, optionsSignal()))
77
- })
78
- })()
55
+ !options?.injector && assertInInjectionContext(injectMutation)
56
+ const injector = options?.injector ?? inject(Injector)
57
+ const destroyRef = injector.get(DestroyRef)
58
+ const ngZone = injector.get(NgZone)
59
+ const queryClient = injector.get(QueryClient)
79
60
 
80
- const mutateFnSignal = computed<
81
- CreateMutateFunction<TData, TError, TVariables, TContext>
82
- >(() => {
83
- const observer = observerSignal()
84
- return (variables, mutateOptions) => {
85
- observer.mutate(variables, mutateOptions).catch(noop)
86
- }
61
+ /**
62
+ * computed() is used so signals can be inserted into the options
63
+ * making it reactive. Wrapping options in a function ensures embedded expressions
64
+ * are preserved and can keep being applied after signal changes
65
+ */
66
+ const optionsSignal = computed(injectMutationFn)
67
+
68
+ const observerSignal = (() => {
69
+ let instance: MutationObserver<TData, TError, TVariables, TContext> | null =
70
+ null
71
+
72
+ return computed(() => {
73
+ return (instance ||= new MutationObserver(queryClient, optionsSignal()))
87
74
  })
75
+ })()
76
+
77
+ const mutateFnSignal = computed<
78
+ CreateMutateFunction<TData, TError, TVariables, TContext>
79
+ >(() => {
80
+ const observer = observerSignal()
81
+ return (variables, mutateOptions) => {
82
+ observer.mutate(variables, mutateOptions).catch(noop)
83
+ }
84
+ })
85
+
86
+ /**
87
+ * Computed signal that gets result from mutation cache based on passed options
88
+ */
89
+ const resultFromInitialOptionsSignal = computed(() => {
90
+ const observer = observerSignal()
91
+ return observer.getCurrentResult()
92
+ })
93
+
94
+ /**
95
+ * Signal that contains result set by subscriber
96
+ */
97
+ const resultFromSubscriberSignal = signal<MutationObserverResult<
98
+ TData,
99
+ TError,
100
+ TVariables,
101
+ TContext
102
+ > | null>(null)
88
103
 
89
- /**
90
- * Computed signal that gets result from mutation cache based on passed options
91
- */
92
- const resultFromInitialOptionsSignal = computed(() => {
104
+ effect(
105
+ () => {
93
106
  const observer = observerSignal()
94
- return observer.getCurrentResult()
95
- })
107
+ const observerOptions = optionsSignal()
96
108
 
97
- /**
98
- * Signal that contains result set by subscriber
99
- */
100
- const resultFromSubscriberSignal = signal<MutationObserverResult<
101
- TData,
102
- TError,
103
- TVariables,
104
- TContext
105
- > | null>(null)
106
-
107
- effect(
108
- () => {
109
- const observer = observerSignal()
110
- const observerOptions = optionsSignal()
111
-
112
- untracked(() => {
113
- observer.setOptions(observerOptions)
114
- })
115
- },
116
- {
117
- injector: options?.injector,
118
- },
119
- )
120
-
121
- effect(
122
- () => {
123
- // observer.trackResult is not used as this optimization is not needed for Angular
124
- const observer = observerSignal()
125
-
126
- untracked(() => {
127
- const unsubscribe = ngZone.runOutsideAngular(() =>
128
- observer.subscribe(
129
- notifyManager.batchCalls((state) => {
130
- ngZone.run(() => {
131
- if (
132
- state.isError &&
133
- shouldThrowError(observer.options.throwOnError, [
134
- state.error,
135
- ])
136
- ) {
137
- ngZone.onError.emit(state.error)
138
- throw state.error
139
- }
140
-
141
- resultFromSubscriberSignal.set(state)
142
- })
143
- }),
144
- ),
145
- )
146
- destroyRef.onDestroy(unsubscribe)
147
- })
148
- },
149
- {
150
- injector: options?.injector,
151
- },
152
- )
153
-
154
- const resultSignal = computed(() => {
155
- const resultFromSubscriber = resultFromSubscriberSignal()
156
- const resultFromInitialOptions = resultFromInitialOptionsSignal()
157
-
158
- const result = resultFromSubscriber ?? resultFromInitialOptions
159
-
160
- return {
161
- ...result,
162
- mutate: mutateFnSignal(),
163
- mutateAsync: result.mutate,
164
- }
165
- })
109
+ untracked(() => {
110
+ observer.setOptions(observerOptions)
111
+ })
112
+ },
113
+ {
114
+ injector,
115
+ },
116
+ )
117
+
118
+ effect(
119
+ () => {
120
+ // observer.trackResult is not used as this optimization is not needed for Angular
121
+ const observer = observerSignal()
166
122
 
167
- return signalProxy(resultSignal) as CreateMutationResult<
168
- TData,
169
- TError,
170
- TVariables,
171
- TContext
172
- >
123
+ untracked(() => {
124
+ const unsubscribe = ngZone.runOutsideAngular(() =>
125
+ observer.subscribe(
126
+ notifyManager.batchCalls((state) => {
127
+ ngZone.run(() => {
128
+ if (
129
+ state.isError &&
130
+ shouldThrowError(observer.options.throwOnError, [state.error])
131
+ ) {
132
+ ngZone.onError.emit(state.error)
133
+ throw state.error
134
+ }
135
+
136
+ resultFromSubscriberSignal.set(state)
137
+ })
138
+ }),
139
+ ),
140
+ )
141
+ destroyRef.onDestroy(unsubscribe)
142
+ })
143
+ },
144
+ {
145
+ injector,
146
+ },
147
+ )
148
+
149
+ const resultSignal = computed(() => {
150
+ const resultFromSubscriber = resultFromSubscriberSignal()
151
+ const resultFromInitialOptions = resultFromInitialOptionsSignal()
152
+
153
+ const result = resultFromSubscriber ?? resultFromInitialOptions
154
+
155
+ return {
156
+ ...result,
157
+ mutate: mutateFnSignal(),
158
+ mutateAsync: result.mutate,
159
+ }
173
160
  })
161
+
162
+ return signalProxy(resultSignal) as CreateMutationResult<
163
+ TData,
164
+ TError,
165
+ TVariables,
166
+ TContext
167
+ >
174
168
  }
@@ -5,14 +5,17 @@ import {
5
5
  } from '@tanstack/query-core'
6
6
  import {
7
7
  DestroyRef,
8
+ Injector,
8
9
  NgZone,
10
+ assertInInjectionContext,
9
11
  computed,
10
12
  effect,
11
13
  inject,
14
+ runInInjectionContext,
12
15
  signal,
13
16
  } from '@angular/core'
14
- import { assertInjector } from './util/assert-injector/assert-injector'
15
- import type { Injector, Signal } from '@angular/core'
17
+ import { injectIsRestoring } from './inject-is-restoring'
18
+ import type { Signal } from '@angular/core'
16
19
  import type {
17
20
  DefaultError,
18
21
  OmitKeyof,
@@ -213,16 +216,20 @@ export function injectQueries<
213
216
  },
214
217
  injector?: Injector,
215
218
  ): Signal<TCombinedResult> {
216
- return assertInjector(injectQueries, injector, () => {
219
+ !injector && assertInInjectionContext(injectQueries)
220
+ return runInInjectionContext(injector ?? inject(Injector), () => {
217
221
  const destroyRef = inject(DestroyRef)
218
222
  const ngZone = inject(NgZone)
219
223
  const queryClient = inject(QueryClient)
224
+ const isRestoring = injectIsRestoring()
220
225
 
221
226
  const defaultedQueries = computed(() => {
222
227
  return queries().map((opts) => {
223
228
  const defaultedOptions = queryClient.defaultQueryOptions(opts)
224
229
  // Make sure the results are already in fetching state before subscribing or updating options
225
- defaultedOptions._optimisticResults = 'optimistic'
230
+ defaultedOptions._optimisticResults = isRestoring()
231
+ ? 'isRestoring'
232
+ : 'optimistic'
226
233
 
227
234
  return defaultedOptions as QueryObserverOptions
228
235
  })
@@ -250,10 +257,14 @@ export function injectQueries<
250
257
 
251
258
  const result = signal(getCombinedResult() as any)
252
259
 
253
- const unsubscribe = ngZone.runOutsideAngular(() =>
254
- observer.subscribe(notifyManager.batchCalls(result.set)),
255
- )
256
- destroyRef.onDestroy(unsubscribe)
260
+ effect(() => {
261
+ const unsubscribe = isRestoring()
262
+ ? () => undefined
263
+ : ngZone.runOutsideAngular(() =>
264
+ observer.subscribe(notifyManager.batchCalls(result.set)),
265
+ )
266
+ destroyRef.onDestroy(unsubscribe)
267
+ })
257
268
 
258
269
  return result
259
270
  })
@@ -1,7 +1,11 @@
1
1
  import { QueryObserver } from '@tanstack/query-core'
2
- import { assertInjector } from './util/assert-injector/assert-injector'
2
+ import {
3
+ Injector,
4
+ assertInInjectionContext,
5
+ inject,
6
+ runInInjectionContext,
7
+ } from '@angular/core'
3
8
  import { createBaseQuery } from './create-base-query'
4
- import type { Injector } from '@angular/core'
5
9
  import type { DefaultError, QueryKey } from '@tanstack/query-core'
6
10
  import type {
7
11
  CreateQueryOptions,
@@ -219,7 +223,8 @@ export function injectQuery(
219
223
  injectQueryFn: () => CreateQueryOptions,
220
224
  options?: InjectQueryOptions,
221
225
  ) {
222
- return assertInjector(injectQuery, options?.injector, () =>
226
+ !options?.injector && assertInInjectionContext(injectQuery)
227
+ return runInInjectionContext(options?.injector ?? inject(Injector), () =>
223
228
  createBaseQuery(injectQueryFn, QueryObserver),
224
229
  ) as unknown as CreateQueryResult
225
230
  }
package/src/providers.ts CHANGED
@@ -140,7 +140,7 @@ export interface QueryFeature<TFeatureKind extends QueryFeatureKind> {
140
140
  * @param providers -
141
141
  * @returns A Query feature.
142
142
  */
143
- function queryFeature<TFeatureKind extends QueryFeatureKind>(
143
+ export function queryFeature<TFeatureKind extends QueryFeatureKind>(
144
144
  kind: TFeatureKind,
145
145
  providers: Array<Provider>,
146
146
  ): QueryFeature<TFeatureKind> {
@@ -155,6 +155,13 @@ function queryFeature<TFeatureKind extends QueryFeatureKind>(
155
155
  */
156
156
  export type DeveloperToolsFeature = QueryFeature<'DeveloperTools'>
157
157
 
158
+ /**
159
+ * A type alias that represents a feature which enables persistence.
160
+ * The type is used to describe the return value of the `withPersistQueryClient` function.
161
+ * @public
162
+ */
163
+ export type PersistQueryClientFeature = QueryFeature<'PersistQueryClient'>
164
+
158
165
  /**
159
166
  * Options for configuring the TanStack Query devtools.
160
167
  * @public
@@ -342,8 +349,8 @@ export function withDevtools(
342
349
  * @public
343
350
  * @see {@link provideTanStackQuery}
344
351
  */
345
- export type QueryFeatures = DeveloperToolsFeature // Union type of features but just one now
352
+ export type QueryFeatures = DeveloperToolsFeature | PersistQueryClientFeature
346
353
 
347
- export const queryFeatures = ['DeveloperTools'] as const
354
+ export const queryFeatures = ['DeveloperTools', 'PersistQueryClient'] as const
348
355
 
349
356
  export type QueryFeatureKind = (typeof queryFeatures)[number]