@tanstack/angular-query-experimental 5.62.3 → 5.62.5

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,18 +1,10 @@
1
- import {
2
- DestroyRef,
3
- NgZone,
4
- effect,
5
- inject,
6
- signal,
7
- untracked,
8
- } from '@angular/core'
1
+ import { DestroyRef, NgZone, computed, inject, signal } from '@angular/core'
9
2
  import {
10
3
  QueryClient,
11
4
  notifyManager,
12
5
  replaceEqualDeep,
13
6
  } from '@tanstack/query-core'
14
7
  import { assertInjector } from './util/assert-injector/assert-injector'
15
- import { lazySignalInitializer } from './util/lazy-signal-initializer/lazy-signal-initializer'
16
8
  import type { Injector, Signal } from '@angular/core'
17
9
  import type {
18
10
  Mutation,
@@ -63,42 +55,55 @@ export function injectMutationState<TResult = MutationState>(
63
55
 
64
56
  const mutationCache = queryClient.getMutationCache()
65
57
 
66
- return lazySignalInitializer((injector) => {
67
- const result = signal<Array<TResult>>(
58
+ /**
59
+ * Computed signal that gets result from mutation cache based on passed options
60
+ * First element is the result, second element is the time when the result was set
61
+ */
62
+ const resultFromOptionsSignal = computed(() => {
63
+ return [
68
64
  getResult(mutationCache, mutationStateOptionsFn()),
69
- )
65
+ performance.now(),
66
+ ] as const
67
+ })
70
68
 
71
- effect(
72
- () => {
73
- const mutationStateOptions = mutationStateOptionsFn()
74
- untracked(() => {
75
- // Setting the signal from an effect because it's both 'computed' from options()
76
- // and needs to be set imperatively in the mutationCache listener.
77
- result.set(getResult(mutationCache, mutationStateOptions))
78
- })
79
- },
80
- { injector },
81
- )
69
+ /**
70
+ * Signal that contains result set by subscriber
71
+ * First element is the result, second element is the time when the result was set
72
+ */
73
+ const resultFromSubscriberSignal = signal<[Array<TResult>, number] | null>(
74
+ null,
75
+ )
82
76
 
83
- const unsubscribe = ngZone.runOutsideAngular(() =>
84
- mutationCache.subscribe(
85
- notifyManager.batchCalls(() => {
86
- const nextResult = replaceEqualDeep(
87
- result(),
88
- getResult(mutationCache, mutationStateOptionsFn()),
89
- )
90
- if (result() !== nextResult) {
91
- ngZone.run(() => {
92
- result.set(nextResult)
93
- })
94
- }
95
- }),
96
- ),
97
- )
77
+ /**
78
+ * Returns the last result by either subscriber or options
79
+ */
80
+ const effectiveResultSignal = computed(() => {
81
+ const optionsResult = resultFromOptionsSignal()
82
+ const subscriberResult = resultFromSubscriberSignal()
83
+ return subscriberResult && subscriberResult[1] > optionsResult[1]
84
+ ? subscriberResult[0]
85
+ : optionsResult[0]
86
+ })
98
87
 
99
- destroyRef.onDestroy(unsubscribe)
88
+ const unsubscribe = ngZone.runOutsideAngular(() =>
89
+ mutationCache.subscribe(
90
+ notifyManager.batchCalls(() => {
91
+ const [lastResult] = effectiveResultSignal()
92
+ const nextResult = replaceEqualDeep(
93
+ lastResult,
94
+ getResult(mutationCache, mutationStateOptionsFn()),
95
+ )
96
+ if (lastResult !== nextResult) {
97
+ ngZone.run(() => {
98
+ resultFromSubscriberSignal.set([nextResult, performance.now()])
99
+ })
100
+ }
101
+ }),
102
+ ),
103
+ )
100
104
 
101
- return result
102
- })
105
+ destroyRef.onDestroy(unsubscribe)
106
+
107
+ return effectiveResultSignal
103
108
  })
104
109
  }
@@ -7,6 +7,7 @@ import {
7
7
  inject,
8
8
  runInInjectionContext,
9
9
  signal,
10
+ untracked,
10
11
  } from '@angular/core'
11
12
  import {
12
13
  MutationObserver,
@@ -16,8 +17,6 @@ import {
16
17
  import { assertInjector } from './util/assert-injector/assert-injector'
17
18
  import { signalProxy } from './signal-proxy'
18
19
  import { noop, shouldThrowError } from './util'
19
-
20
- import { lazyInit } from './util/lazy-init/lazy-init'
21
20
  import type { DefaultError, MutationObserverResult } from '@tanstack/query-core'
22
21
  import type { CreateMutateFunction, CreateMutationResult } from './types'
23
22
  import type { CreateMutationOptions } from './mutation-options'
@@ -46,42 +45,78 @@ export function injectMutation<
46
45
  const ngZone = inject(NgZone)
47
46
  const queryClient = inject(QueryClient)
48
47
 
49
- return lazyInit(() =>
50
- runInInjectionContext(currentInjector, () => {
51
- const observer = new MutationObserver<
52
- TData,
53
- TError,
54
- TVariables,
55
- TContext
56
- >(queryClient, optionsFn())
57
- const mutate: CreateMutateFunction<
58
- TData,
59
- TError,
60
- TVariables,
61
- TContext
62
- > = (variables, mutateOptions) => {
63
- observer.mutate(variables, mutateOptions).catch(noop)
64
- }
65
-
66
- effect(() => {
67
- observer.setOptions(
68
- runInInjectionContext(currentInjector, () => optionsFn()),
69
- )
48
+ /**
49
+ * computed() is used so signals can be inserted into the options
50
+ * making it reactive. Wrapping options in a function ensures embedded expressions
51
+ * are preserved and can keep being applied after signal changes
52
+ */
53
+ const optionsSignal = computed(() =>
54
+ runInInjectionContext(currentInjector, () => optionsFn()),
55
+ )
56
+
57
+ const observerSignal = (() => {
58
+ let instance: MutationObserver<
59
+ TData,
60
+ TError,
61
+ TVariables,
62
+ TContext
63
+ > | null = null
64
+
65
+ return computed(() => {
66
+ return (instance ||= new MutationObserver(queryClient, optionsSignal()))
67
+ })
68
+ })()
69
+
70
+ const mutateFnSignal = computed<
71
+ CreateMutateFunction<TData, TError, TVariables, TContext>
72
+ >(() => {
73
+ const observer = observerSignal()
74
+ return (variables, mutateOptions) => {
75
+ observer.mutate(variables, mutateOptions).catch(noop)
76
+ }
77
+ })
78
+
79
+ /**
80
+ * Computed signal that gets result from mutation cache based on passed options
81
+ */
82
+ const resultFromInitialOptionsSignal = computed(() => {
83
+ const observer = observerSignal()
84
+ return observer.getCurrentResult()
85
+ })
86
+
87
+ /**
88
+ * Signal that contains result set by subscriber
89
+ */
90
+ const resultFromSubscriberSignal = signal<MutationObserverResult<
91
+ TData,
92
+ TError,
93
+ TVariables,
94
+ TContext
95
+ > | null>(null)
96
+
97
+ effect(
98
+ () => {
99
+ const observer = observerSignal()
100
+ const options = optionsSignal()
101
+
102
+ untracked(() => {
103
+ observer.setOptions(options)
70
104
  })
105
+ },
106
+ {
107
+ injector,
108
+ },
109
+ )
110
+
111
+ effect(
112
+ () => {
113
+ // observer.trackResult is not used as this optimization is not needed for Angular
114
+ const observer = observerSignal()
71
115
 
72
- const result = signal(observer.getCurrentResult())
73
-
74
- const unsubscribe = ngZone.runOutsideAngular(() =>
75
- observer.subscribe(
76
- notifyManager.batchCalls(
77
- (
78
- state: MutationObserverResult<
79
- TData,
80
- TError,
81
- TVariables,
82
- TContext
83
- >,
84
- ) => {
116
+ untracked(() => {
117
+ const unsubscribe = ngZone.runOutsideAngular(() =>
118
+ observer.subscribe(
119
+ notifyManager.batchCalls((state) => {
85
120
  ngZone.run(() => {
86
121
  if (
87
122
  state.isError &&
@@ -91,28 +126,38 @@ export function injectMutation<
91
126
  ) {
92
127
  throw state.error
93
128
  }
94
- result.set(state)
129
+
130
+ resultFromSubscriberSignal.set(state)
95
131
  })
96
- },
132
+ }),
97
133
  ),
98
- ),
99
- )
100
-
101
- destroyRef.onDestroy(unsubscribe)
102
-
103
- const resultSignal = computed(() => ({
104
- ...result(),
105
- mutate,
106
- mutateAsync: result().mutate,
107
- }))
108
-
109
- return signalProxy(resultSignal) as unknown as CreateMutationResult<
110
- TData,
111
- TError,
112
- TVariables,
113
- TContext
114
- >
115
- }),
134
+ )
135
+ destroyRef.onDestroy(unsubscribe)
136
+ })
137
+ },
138
+ {
139
+ injector,
140
+ },
116
141
  )
142
+
143
+ const resultSignal = computed(() => {
144
+ const resultFromSubscriber = resultFromSubscriberSignal()
145
+ const resultFromInitialOptions = resultFromInitialOptionsSignal()
146
+
147
+ const result = resultFromSubscriber ?? resultFromInitialOptions
148
+
149
+ return {
150
+ ...result,
151
+ mutate: mutateFnSignal(),
152
+ mutateAsync: result.mutate,
153
+ }
154
+ })
155
+
156
+ return signalProxy(resultSignal) as CreateMutationResult<
157
+ TData,
158
+ TError,
159
+ TVariables,
160
+ TContext
161
+ >
117
162
  })
118
163
  }
package/src/providers.ts CHANGED
@@ -99,6 +99,7 @@ export function provideTanStackQuery(
99
99
  return makeEnvironmentProviders([
100
100
  provideQueryClient(queryClient),
101
101
  {
102
+ // Do not use provideEnvironmentInitializer to support Angular < v19
102
103
  provide: ENVIRONMENT_INITIALIZER,
103
104
  multi: true,
104
105
  useValue: () => {
@@ -1,34 +0,0 @@
1
- import { untracked } from '@angular/core'
2
-
3
- export function lazyInit<T extends object>(initializer: () => T): T {
4
- let object: T | null = null
5
-
6
- const initializeObject = () => {
7
- if (!object) {
8
- object = untracked(() => initializer())
9
- }
10
- }
11
-
12
- queueMicrotask(() => initializeObject())
13
-
14
- return new Proxy<T>({} as T, {
15
- get(_, prop, receiver) {
16
- initializeObject()
17
- return Reflect.get(object as T, prop, receiver)
18
- },
19
- has(_, prop) {
20
- initializeObject()
21
- return Reflect.has(object as T, prop)
22
- },
23
- ownKeys() {
24
- initializeObject()
25
- return Reflect.ownKeys(object as T)
26
- },
27
- getOwnPropertyDescriptor() {
28
- return {
29
- enumerable: true,
30
- configurable: true,
31
- }
32
- },
33
- })
34
- }
@@ -1,23 +0,0 @@
1
- import { Injector, computed, inject, untracked } from '@angular/core'
2
- import type { Signal } from '@angular/core'
3
-
4
- type SignalInitializerFn<T> = (injector: Injector) => Signal<T>
5
-
6
- export function lazySignalInitializer<T>(
7
- initializerFn: SignalInitializerFn<T>,
8
- ) {
9
- const injector = inject(Injector)
10
-
11
- let source: Signal<T> | null = null
12
-
13
- const unwrapSignal = () => {
14
- if (!source) {
15
- source = untracked(() => initializerFn(injector))
16
- }
17
- return source()
18
- }
19
-
20
- queueMicrotask(() => unwrapSignal())
21
-
22
- return computed(unwrapSignal)
23
- }