diff --git a/examples/angular/auto-refetching/package.json b/examples/angular/auto-refetching/package.json index 2d1c0811396..099d9bff344 100644 --- a/examples/angular/auto-refetching/package.json +++ b/examples/angular/auto-refetching/package.json @@ -14,7 +14,7 @@ "@angular/core": "^19.2.4", "@angular/platform-browser": "^19.2.4", "@angular/platform-browser-dynamic": "^19.2.4", - "@tanstack/angular-query-experimental": "^5.74.0", + "@tanstack/angular-query-experimental": "^5.74.1", "rxjs": "^7.8.2", "tslib": "^2.8.1", "zone.js": "0.15.0" diff --git a/examples/angular/basic-persister/package.json b/examples/angular/basic-persister/package.json index a8056751980..fe9f0d62029 100644 --- a/examples/angular/basic-persister/package.json +++ b/examples/angular/basic-persister/package.json @@ -14,7 +14,7 @@ "@angular/core": "^19.2.4", "@angular/platform-browser": "^19.2.4", "@angular/platform-browser-dynamic": "^19.2.4", - "@tanstack/angular-query-experimental": "^5.74.0", + "@tanstack/angular-query-experimental": "^5.74.1", "@tanstack/angular-query-persist-client": "^5.62.7", "@tanstack/query-sync-storage-persister": "^5.74.0", "rxjs": "^7.8.2", diff --git a/examples/angular/basic/package.json b/examples/angular/basic/package.json index 616a81c04cd..2cadc4cdf21 100644 --- a/examples/angular/basic/package.json +++ b/examples/angular/basic/package.json @@ -14,7 +14,7 @@ "@angular/core": "^19.2.4", "@angular/platform-browser": "^19.2.4", "@angular/platform-browser-dynamic": "^19.2.4", - "@tanstack/angular-query-experimental": "^5.74.0", + "@tanstack/angular-query-experimental": "^5.74.1", "rxjs": "^7.8.2", "tslib": "^2.8.1", "zone.js": "0.15.0" diff --git a/examples/angular/devtools-panel/package.json b/examples/angular/devtools-panel/package.json index 8fc2d132e31..490c82dddb2 100644 --- a/examples/angular/devtools-panel/package.json +++ b/examples/angular/devtools-panel/package.json @@ -15,8 +15,8 @@ "@angular/platform-browser": "^19.2.4", "@angular/platform-browser-dynamic": "^19.2.4", "@angular/router": "^19.2.4", - "@tanstack/angular-query-devtools-experimental": "^5.74.0", - "@tanstack/angular-query-experimental": "^5.74.0", + "@tanstack/angular-query-devtools-experimental": "^5.74.1", + "@tanstack/angular-query-experimental": "^5.74.1", "rxjs": "^7.8.2", "tslib": "^2.8.1", "zone.js": "0.15.0" diff --git a/examples/angular/infinite-query-with-max-pages/package.json b/examples/angular/infinite-query-with-max-pages/package.json index 7b8a1a5c9c7..76c0b3d7b78 100644 --- a/examples/angular/infinite-query-with-max-pages/package.json +++ b/examples/angular/infinite-query-with-max-pages/package.json @@ -14,7 +14,7 @@ "@angular/core": "^19.2.4", "@angular/platform-browser": "^19.2.4", "@angular/platform-browser-dynamic": "^19.2.4", - "@tanstack/angular-query-experimental": "^5.74.0", + "@tanstack/angular-query-experimental": "^5.74.1", "rxjs": "^7.8.2", "tslib": "^2.8.1", "zone.js": "0.15.0" diff --git a/examples/angular/optimistic-updates/package.json b/examples/angular/optimistic-updates/package.json index d76c51d288e..1d87b96f0cf 100644 --- a/examples/angular/optimistic-updates/package.json +++ b/examples/angular/optimistic-updates/package.json @@ -15,7 +15,7 @@ "@angular/forms": "^19.2.4", "@angular/platform-browser": "^19.2.4", "@angular/platform-browser-dynamic": "^19.2.4", - "@tanstack/angular-query-experimental": "^5.74.0", + "@tanstack/angular-query-experimental": "^5.74.1", "rxjs": "^7.8.2", "tslib": "^2.8.1", "zone.js": "0.15.0" diff --git a/examples/angular/pagination/package.json b/examples/angular/pagination/package.json index 4dd3aee8273..94cd6430556 100644 --- a/examples/angular/pagination/package.json +++ b/examples/angular/pagination/package.json @@ -14,7 +14,7 @@ "@angular/core": "^19.2.4", "@angular/platform-browser": "^19.2.4", "@angular/platform-browser-dynamic": "^19.2.4", - "@tanstack/angular-query-experimental": "^5.74.0", + "@tanstack/angular-query-experimental": "^5.74.1", "rxjs": "^7.8.2", "tslib": "^2.8.1", "zone.js": "0.15.0" diff --git a/examples/angular/query-options-from-a-service/package.json b/examples/angular/query-options-from-a-service/package.json index dbe1b922f9f..2eac7566b5b 100644 --- a/examples/angular/query-options-from-a-service/package.json +++ b/examples/angular/query-options-from-a-service/package.json @@ -15,7 +15,7 @@ "@angular/platform-browser": "^19.2.4", "@angular/platform-browser-dynamic": "^19.2.4", "@angular/router": "^19.2.4", - "@tanstack/angular-query-experimental": "^5.74.0", + "@tanstack/angular-query-experimental": "^5.74.1", "rxjs": "^7.8.2", "tslib": "^2.8.1", "zone.js": "0.15.0" diff --git a/examples/angular/router/package.json b/examples/angular/router/package.json index 120e5517c03..136e5252266 100644 --- a/examples/angular/router/package.json +++ b/examples/angular/router/package.json @@ -15,7 +15,7 @@ "@angular/platform-browser": "^19.2.4", "@angular/platform-browser-dynamic": "^19.2.4", "@angular/router": "^19.2.4", - "@tanstack/angular-query-experimental": "^5.74.0", + "@tanstack/angular-query-experimental": "^5.74.1", "rxjs": "^7.8.2", "tslib": "^2.8.1", "zone.js": "0.15.0" diff --git a/examples/angular/rxjs/package.json b/examples/angular/rxjs/package.json index bee0e6ebbdd..9f4bd9a786b 100644 --- a/examples/angular/rxjs/package.json +++ b/examples/angular/rxjs/package.json @@ -15,7 +15,7 @@ "@angular/forms": "^19.2.4", "@angular/platform-browser": "^19.2.4", "@angular/platform-browser-dynamic": "^19.2.4", - "@tanstack/angular-query-experimental": "^5.74.0", + "@tanstack/angular-query-experimental": "^5.74.1", "rxjs": "^7.8.2", "tslib": "^2.8.1", "zone.js": "0.15.0" diff --git a/examples/angular/simple/package.json b/examples/angular/simple/package.json index 6a6d8d42168..eab910da811 100644 --- a/examples/angular/simple/package.json +++ b/examples/angular/simple/package.json @@ -14,7 +14,7 @@ "@angular/core": "^19.2.4", "@angular/platform-browser": "^19.2.4", "@angular/platform-browser-dynamic": "^19.2.4", - "@tanstack/angular-query-experimental": "^5.74.0", + "@tanstack/angular-query-experimental": "^5.74.1", "rxjs": "^7.8.2", "tslib": "^2.8.1", "zone.js": "0.15.0" diff --git a/packages/angular-query-devtools-experimental/package.json b/packages/angular-query-devtools-experimental/package.json index fc4b443c44a..982bed3ea5e 100644 --- a/packages/angular-query-devtools-experimental/package.json +++ b/packages/angular-query-devtools-experimental/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/angular-query-devtools-experimental", - "version": "5.74.0", + "version": "5.74.1", "description": "Developer tools to interact with and visualize the TanStack/angular-query cache", "author": "Arnoud de Vries", "license": "MIT", diff --git a/packages/angular-query-experimental/package.json b/packages/angular-query-experimental/package.json index bc0b77147fb..80934e5634a 100644 --- a/packages/angular-query-experimental/package.json +++ b/packages/angular-query-experimental/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/angular-query-experimental", - "version": "5.74.0", + "version": "5.74.1", "description": "Signals for managing, caching and syncing asynchronous and remote data in Angular", "author": "Arnoud de Vries", "license": "MIT", diff --git a/packages/angular-query-experimental/src/inject-infinite-query.ts b/packages/angular-query-experimental/src/inject-infinite-query.ts index 8f6bb75ed11..6993a5bfcc5 100644 --- a/packages/angular-query-experimental/src/inject-infinite-query.ts +++ b/packages/angular-query-experimental/src/inject-infinite-query.ts @@ -1,6 +1,11 @@ import { InfiniteQueryObserver } from '@tanstack/query-core' +import { + Injector, + assertInInjectionContext, + inject, + runInInjectionContext, +} from '@angular/core' import { createBaseQuery } from './create-base-query' -import { assertInjector } from './util/assert-injector/assert-injector' import type { DefaultError, InfiniteData, @@ -16,7 +21,6 @@ import type { DefinedInitialDataInfiniteOptions, UndefinedInitialDataInfiniteOptions, } from './infinite-query-options' -import type { Injector } from '@angular/core' export interface InjectInfiniteQueryOptions { /** @@ -115,14 +119,12 @@ export function injectInfiniteQuery( injectInfiniteQueryFn: () => CreateInfiniteQueryOptions, options?: InjectInfiniteQueryOptions, ) { - return assertInjector(injectInfiniteQuery, options?.injector, () => + !options?.injector && assertInInjectionContext(injectInfiniteQuery) + const injector = options?.injector ?? inject(Injector) + return runInInjectionContext(injector, () => createBaseQuery( injectInfiniteQueryFn, InfiniteQueryObserver as typeof QueryObserver, ), ) } - -export interface InjectInfiniteQueryOptions { - injector?: Injector -} diff --git a/packages/angular-query-experimental/src/inject-is-fetching.ts b/packages/angular-query-experimental/src/inject-is-fetching.ts index 615f7077b95..30d0a8517ee 100644 --- a/packages/angular-query-experimental/src/inject-is-fetching.ts +++ b/packages/angular-query-experimental/src/inject-is-fetching.ts @@ -1,8 +1,14 @@ -import { DestroyRef, NgZone, inject, signal } from '@angular/core' +import { + DestroyRef, + Injector, + NgZone, + assertInInjectionContext, + inject, + signal, +} from '@angular/core' import { QueryClient, notifyManager } from '@tanstack/query-core' -import { assertInjector } from './util/assert-injector/assert-injector' import type { QueryFilters } from '@tanstack/query-core' -import type { Injector, Signal } from '@angular/core' +import type { Signal } from '@angular/core' export interface InjectIsFetchingOptions { /** @@ -27,34 +33,34 @@ export function injectIsFetching( filters?: QueryFilters, options?: InjectIsFetchingOptions, ): Signal { - return assertInjector(injectIsFetching, options?.injector, () => { - const destroyRef = inject(DestroyRef) - const ngZone = inject(NgZone) - const queryClient = inject(QueryClient) + !options?.injector && assertInInjectionContext(injectIsFetching) + const injector = options?.injector ?? inject(Injector) + const destroyRef = injector.get(DestroyRef) + const ngZone = injector.get(NgZone) + const queryClient = injector.get(QueryClient) - const cache = queryClient.getQueryCache() - // isFetching is the prev value initialized on mount * - let isFetching = queryClient.isFetching(filters) + const cache = queryClient.getQueryCache() + // isFetching is the prev value initialized on mount * + let isFetching = queryClient.isFetching(filters) - const result = signal(isFetching) + const result = signal(isFetching) - const unsubscribe = ngZone.runOutsideAngular(() => - cache.subscribe( - notifyManager.batchCalls(() => { - const newIsFetching = queryClient.isFetching(filters) - if (isFetching !== newIsFetching) { - // * and update with each change - isFetching = newIsFetching - ngZone.run(() => { - result.set(isFetching) - }) - } - }), - ), - ) + const unsubscribe = ngZone.runOutsideAngular(() => + cache.subscribe( + notifyManager.batchCalls(() => { + const newIsFetching = queryClient.isFetching(filters) + if (isFetching !== newIsFetching) { + // * and update with each change + isFetching = newIsFetching + ngZone.run(() => { + result.set(isFetching) + }) + } + }), + ), + ) - destroyRef.onDestroy(unsubscribe) + destroyRef.onDestroy(unsubscribe) - return result - }) + return result } diff --git a/packages/angular-query-experimental/src/inject-is-mutating.ts b/packages/angular-query-experimental/src/inject-is-mutating.ts index df6af1dea47..21de13b90e2 100644 --- a/packages/angular-query-experimental/src/inject-is-mutating.ts +++ b/packages/angular-query-experimental/src/inject-is-mutating.ts @@ -1,8 +1,14 @@ -import { DestroyRef, NgZone, inject, signal } from '@angular/core' +import { + DestroyRef, + Injector, + NgZone, + assertInInjectionContext, + inject, + signal, +} from '@angular/core' import { QueryClient, notifyManager } from '@tanstack/query-core' -import { assertInjector } from './util/assert-injector/assert-injector' import type { MutationFilters } from '@tanstack/query-core' -import type { Injector, Signal } from '@angular/core' +import type { Signal } from '@angular/core' export interface InjectIsMutatingOptions { /** @@ -26,34 +32,34 @@ export function injectIsMutating( filters?: MutationFilters, options?: InjectIsMutatingOptions, ): Signal { - return assertInjector(injectIsMutating, options?.injector, () => { - const destroyRef = inject(DestroyRef) - const ngZone = inject(NgZone) - const queryClient = inject(QueryClient) + !options?.injector && assertInInjectionContext(injectIsMutating) + const injector = options?.injector ?? inject(Injector) + const destroyRef = injector.get(DestroyRef) + const ngZone = injector.get(NgZone) + const queryClient = injector.get(QueryClient) - const cache = queryClient.getMutationCache() - // isMutating is the prev value initialized on mount * - let isMutating = queryClient.isMutating(filters) + const cache = queryClient.getMutationCache() + // isMutating is the prev value initialized on mount * + let isMutating = queryClient.isMutating(filters) - const result = signal(isMutating) + const result = signal(isMutating) - const unsubscribe = ngZone.runOutsideAngular(() => - cache.subscribe( - notifyManager.batchCalls(() => { - const newIsMutating = queryClient.isMutating(filters) - if (isMutating !== newIsMutating) { - // * and update with each change - isMutating = newIsMutating - ngZone.run(() => { - result.set(isMutating) - }) - } - }), - ), - ) + const unsubscribe = ngZone.runOutsideAngular(() => + cache.subscribe( + notifyManager.batchCalls(() => { + const newIsMutating = queryClient.isMutating(filters) + if (isMutating !== newIsMutating) { + // * and update with each change + isMutating = newIsMutating + ngZone.run(() => { + result.set(isMutating) + }) + } + }), + ), + ) - destroyRef.onDestroy(unsubscribe) + destroyRef.onDestroy(unsubscribe) - return result - }) + return result } diff --git a/packages/angular-query-experimental/src/inject-is-restoring.ts b/packages/angular-query-experimental/src/inject-is-restoring.ts index aef8b7a532e..c57a15fbb71 100644 --- a/packages/angular-query-experimental/src/inject-is-restoring.ts +++ b/packages/angular-query-experimental/src/inject-is-restoring.ts @@ -7,7 +7,7 @@ import { } from '@angular/core' import type { Provider, Signal } from '@angular/core' -const IsRestoring = new InjectionToken>('IsRestoring') +const IS_RESTORING = new InjectionToken>('') /** * The `Injector` in which to create the isRestoring signal. @@ -30,7 +30,7 @@ export function injectIsRestoring( !options?.injector && assertInInjectionContext(injectIsRestoring) const injector = options?.injector ?? inject(Injector) return injector.get( - IsRestoring, + IS_RESTORING, computed(() => false), { optional: true }, ) @@ -44,7 +44,7 @@ export function injectIsRestoring( */ export function provideIsRestoring(isRestoring: Signal): Provider { return { - provide: IsRestoring, + provide: IS_RESTORING, useValue: isRestoring, } } diff --git a/packages/angular-query-experimental/src/inject-mutation-state.ts b/packages/angular-query-experimental/src/inject-mutation-state.ts index 5377f271edd..89419f10378 100644 --- a/packages/angular-query-experimental/src/inject-mutation-state.ts +++ b/packages/angular-query-experimental/src/inject-mutation-state.ts @@ -1,11 +1,18 @@ -import { DestroyRef, NgZone, computed, inject, signal } from '@angular/core' +import { + DestroyRef, + Injector, + NgZone, + assertInInjectionContext, + computed, + inject, + signal, +} from '@angular/core' import { QueryClient, notifyManager, replaceEqualDeep, } from '@tanstack/query-core' -import { assertInjector } from './util/assert-injector/assert-injector' -import type { Injector, Signal } from '@angular/core' +import type { Signal } from '@angular/core' import type { Mutation, MutationCache, @@ -58,62 +65,61 @@ export function injectMutationState( injectMutationStateFn: () => MutationStateOptions = () => ({}), options?: InjectMutationStateOptions, ): Signal> { - return assertInjector(injectMutationState, options?.injector, () => { - const destroyRef = inject(DestroyRef) - const ngZone = inject(NgZone) - const queryClient = inject(QueryClient) - - const mutationCache = queryClient.getMutationCache() + !options?.injector && assertInInjectionContext(injectMutationState) + const injector = options?.injector ?? inject(Injector) + const destroyRef = injector.get(DestroyRef) + const ngZone = injector.get(NgZone) + const queryClient = injector.get(QueryClient) + const mutationCache = queryClient.getMutationCache() - /** - * Computed signal that gets result from mutation cache based on passed options - * First element is the result, second element is the time when the result was set - */ - const resultFromOptionsSignal = computed(() => { - return [ - getResult(mutationCache, injectMutationStateFn()), - performance.now(), - ] as const - }) + /** + * Computed signal that gets result from mutation cache based on passed options + * First element is the result, second element is the time when the result was set + */ + const resultFromOptionsSignal = computed(() => { + return [ + getResult(mutationCache, injectMutationStateFn()), + performance.now(), + ] as const + }) - /** - * Signal that contains result set by subscriber - * First element is the result, second element is the time when the result was set - */ - const resultFromSubscriberSignal = signal<[Array, number] | null>( - null, - ) + /** + * Signal that contains result set by subscriber + * First element is the result, second element is the time when the result was set + */ + const resultFromSubscriberSignal = signal<[Array, number] | null>( + null, + ) - /** - * Returns the last result by either subscriber or options - */ - const effectiveResultSignal = computed(() => { - const optionsResult = resultFromOptionsSignal() - const subscriberResult = resultFromSubscriberSignal() - return subscriberResult && subscriberResult[1] > optionsResult[1] - ? subscriberResult[0] - : optionsResult[0] - }) + /** + * Returns the last result by either subscriber or options + */ + const effectiveResultSignal = computed(() => { + const optionsResult = resultFromOptionsSignal() + const subscriberResult = resultFromSubscriberSignal() + return subscriberResult && subscriberResult[1] > optionsResult[1] + ? subscriberResult[0] + : optionsResult[0] + }) - const unsubscribe = ngZone.runOutsideAngular(() => - mutationCache.subscribe( - notifyManager.batchCalls(() => { - const [lastResult] = effectiveResultSignal() - const nextResult = replaceEqualDeep( - lastResult, - getResult(mutationCache, injectMutationStateFn()), - ) - if (lastResult !== nextResult) { - ngZone.run(() => { - resultFromSubscriberSignal.set([nextResult, performance.now()]) - }) - } - }), - ), - ) + const unsubscribe = ngZone.runOutsideAngular(() => + mutationCache.subscribe( + notifyManager.batchCalls(() => { + const [lastResult] = effectiveResultSignal() + const nextResult = replaceEqualDeep( + lastResult, + getResult(mutationCache, injectMutationStateFn()), + ) + if (lastResult !== nextResult) { + ngZone.run(() => { + resultFromSubscriberSignal.set([nextResult, performance.now()]) + }) + } + }), + ), + ) - destroyRef.onDestroy(unsubscribe) + destroyRef.onDestroy(unsubscribe) - return effectiveResultSignal - }) + return effectiveResultSignal } diff --git a/packages/angular-query-experimental/src/inject-mutation.ts b/packages/angular-query-experimental/src/inject-mutation.ts index 9daafb5d716..79133942802 100644 --- a/packages/angular-query-experimental/src/inject-mutation.ts +++ b/packages/angular-query-experimental/src/inject-mutation.ts @@ -1,6 +1,8 @@ import { DestroyRef, + Injector, NgZone, + assertInInjectionContext, computed, effect, inject, @@ -12,10 +14,8 @@ import { QueryClient, notifyManager, } from '@tanstack/query-core' -import { assertInjector } from './util/assert-injector/assert-injector' import { signalProxy } from './signal-proxy' import { noop, shouldThrowError } from './util' -import type { Injector } from '@angular/core' import type { DefaultError, MutationObserverResult } from '@tanstack/query-core' import type { CreateMutateFunction, CreateMutationResult } from './types' import type { CreateMutationOptions } from './mutation-options' @@ -52,123 +52,117 @@ export function injectMutation< >, options?: InjectMutationOptions, ): CreateMutationResult { - return assertInjector(injectMutation, options?.injector, () => { - const destroyRef = inject(DestroyRef) - const ngZone = inject(NgZone) - const queryClient = inject(QueryClient) - - /** - * computed() is used so signals can be inserted into the options - * making it reactive. Wrapping options in a function ensures embedded expressions - * are preserved and can keep being applied after signal changes - */ - const optionsSignal = computed(injectMutationFn) - - const observerSignal = (() => { - let instance: MutationObserver< - TData, - TError, - TVariables, - TContext - > | null = null - - return computed(() => { - return (instance ||= new MutationObserver(queryClient, optionsSignal())) - }) - })() + !options?.injector && assertInInjectionContext(injectMutation) + const injector = options?.injector ?? inject(Injector) + const destroyRef = injector.get(DestroyRef) + const ngZone = injector.get(NgZone) + const queryClient = injector.get(QueryClient) - const mutateFnSignal = computed< - CreateMutateFunction - >(() => { - const observer = observerSignal() - return (variables, mutateOptions) => { - observer.mutate(variables, mutateOptions).catch(noop) - } + /** + * computed() is used so signals can be inserted into the options + * making it reactive. Wrapping options in a function ensures embedded expressions + * are preserved and can keep being applied after signal changes + */ + const optionsSignal = computed(injectMutationFn) + + const observerSignal = (() => { + let instance: MutationObserver | null = + null + + return computed(() => { + return (instance ||= new MutationObserver(queryClient, optionsSignal())) }) + })() + + const mutateFnSignal = computed< + CreateMutateFunction + >(() => { + const observer = observerSignal() + return (variables, mutateOptions) => { + observer.mutate(variables, mutateOptions).catch(noop) + } + }) + + /** + * Computed signal that gets result from mutation cache based on passed options + */ + const resultFromInitialOptionsSignal = computed(() => { + const observer = observerSignal() + return observer.getCurrentResult() + }) + + /** + * Signal that contains result set by subscriber + */ + const resultFromSubscriberSignal = signal | null>(null) - /** - * Computed signal that gets result from mutation cache based on passed options - */ - const resultFromInitialOptionsSignal = computed(() => { + effect( + () => { const observer = observerSignal() - return observer.getCurrentResult() - }) + const observerOptions = optionsSignal() - /** - * Signal that contains result set by subscriber - */ - const resultFromSubscriberSignal = signal | null>(null) - - effect( - () => { - const observer = observerSignal() - const observerOptions = optionsSignal() - - untracked(() => { - observer.setOptions(observerOptions) - }) - }, - { - injector: options?.injector, - }, - ) - - effect( - () => { - // observer.trackResult is not used as this optimization is not needed for Angular - const observer = observerSignal() - - untracked(() => { - const unsubscribe = ngZone.runOutsideAngular(() => - observer.subscribe( - notifyManager.batchCalls((state) => { - ngZone.run(() => { - if ( - state.isError && - shouldThrowError(observer.options.throwOnError, [ - state.error, - ]) - ) { - ngZone.onError.emit(state.error) - throw state.error - } - - resultFromSubscriberSignal.set(state) - }) - }), - ), - ) - destroyRef.onDestroy(unsubscribe) - }) - }, - { - injector: options?.injector, - }, - ) - - const resultSignal = computed(() => { - const resultFromSubscriber = resultFromSubscriberSignal() - const resultFromInitialOptions = resultFromInitialOptionsSignal() - - const result = resultFromSubscriber ?? resultFromInitialOptions - - return { - ...result, - mutate: mutateFnSignal(), - mutateAsync: result.mutate, - } - }) + untracked(() => { + observer.setOptions(observerOptions) + }) + }, + { + injector, + }, + ) + + effect( + () => { + // observer.trackResult is not used as this optimization is not needed for Angular + const observer = observerSignal() - return signalProxy(resultSignal) as CreateMutationResult< - TData, - TError, - TVariables, - TContext - > + untracked(() => { + const unsubscribe = ngZone.runOutsideAngular(() => + observer.subscribe( + notifyManager.batchCalls((state) => { + ngZone.run(() => { + if ( + state.isError && + shouldThrowError(observer.options.throwOnError, [state.error]) + ) { + ngZone.onError.emit(state.error) + throw state.error + } + + resultFromSubscriberSignal.set(state) + }) + }), + ), + ) + destroyRef.onDestroy(unsubscribe) + }) + }, + { + injector, + }, + ) + + const resultSignal = computed(() => { + const resultFromSubscriber = resultFromSubscriberSignal() + const resultFromInitialOptions = resultFromInitialOptionsSignal() + + const result = resultFromSubscriber ?? resultFromInitialOptions + + return { + ...result, + mutate: mutateFnSignal(), + mutateAsync: result.mutate, + } }) + + return signalProxy(resultSignal) as CreateMutationResult< + TData, + TError, + TVariables, + TContext + > } diff --git a/packages/angular-query-experimental/src/inject-queries.ts b/packages/angular-query-experimental/src/inject-queries.ts index 677e9db222b..1889e9560cc 100644 --- a/packages/angular-query-experimental/src/inject-queries.ts +++ b/packages/angular-query-experimental/src/inject-queries.ts @@ -5,15 +5,17 @@ import { } from '@tanstack/query-core' import { DestroyRef, + Injector, NgZone, + assertInInjectionContext, computed, effect, inject, + runInInjectionContext, signal, } from '@angular/core' -import { assertInjector } from './util/assert-injector/assert-injector' import { injectIsRestoring } from './inject-is-restoring' -import type { Injector, Signal } from '@angular/core' +import type { Signal } from '@angular/core' import type { DefaultError, OmitKeyof, @@ -214,7 +216,8 @@ export function injectQueries< }, injector?: Injector, ): Signal { - return assertInjector(injectQueries, injector, () => { + !injector && assertInInjectionContext(injectQueries) + return runInInjectionContext(injector ?? inject(Injector), () => { const destroyRef = inject(DestroyRef) const ngZone = inject(NgZone) const queryClient = inject(QueryClient) diff --git a/packages/angular-query-experimental/src/inject-query.ts b/packages/angular-query-experimental/src/inject-query.ts index e5a19e8a298..0defdf18e93 100644 --- a/packages/angular-query-experimental/src/inject-query.ts +++ b/packages/angular-query-experimental/src/inject-query.ts @@ -1,7 +1,11 @@ import { QueryObserver } from '@tanstack/query-core' -import { assertInjector } from './util/assert-injector/assert-injector' +import { + Injector, + assertInInjectionContext, + inject, + runInInjectionContext, +} from '@angular/core' import { createBaseQuery } from './create-base-query' -import type { Injector } from '@angular/core' import type { DefaultError, QueryKey } from '@tanstack/query-core' import type { CreateQueryOptions, @@ -219,7 +223,8 @@ export function injectQuery( injectQueryFn: () => CreateQueryOptions, options?: InjectQueryOptions, ) { - return assertInjector(injectQuery, options?.injector, () => + !options?.injector && assertInInjectionContext(injectQuery) + return runInInjectionContext(options?.injector ?? inject(Injector), () => createBaseQuery(injectQueryFn, QueryObserver), ) as unknown as CreateQueryResult } diff --git a/packages/angular-query-experimental/src/util/assert-injector/assert-injector.test.ts b/packages/angular-query-experimental/src/util/assert-injector/assert-injector.test.ts deleted file mode 100644 index 708034dff76..00000000000 --- a/packages/angular-query-experimental/src/util/assert-injector/assert-injector.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -/* eslint-disable cspell/spellchecker */ -/** - * The code in this file is adapted from NG Extension Platform at https://ngxtension.netlify.app. - * - * Original Author: Chau Tran - * - * NG Extension Platform is an open-source project licensed under the MIT license. - * - * For more information about the original code, see - * https://github.com/nartc/ngxtension-platform - */ -/* eslint-enable */ - -import { - InjectionToken, - Injector, - inject, - provideExperimentalZonelessChangeDetection, - runInInjectionContext, -} from '@angular/core' -import { TestBed } from '@angular/core/testing' -import { assertInjector } from './assert-injector' - -describe('assertInjector', () => { - const token = new InjectionToken('token', { - factory: () => 1, - }) - - function injectDummy(injector?: Injector) { - injector = assertInjector(injectDummy, injector) - return runInInjectionContext(injector, () => inject(token)) - } - - function injectDummyTwo(injector?: Injector) { - return assertInjector(injectDummyTwo, injector, () => inject(token) + 1) - } - - it('given no custom injector, when run in injection context, then return value', () => { - TestBed.configureTestingModule({ - providers: [provideExperimentalZonelessChangeDetection()], - }) - TestBed.runInInjectionContext(() => { - const value = injectDummy() - const valueTwo = injectDummyTwo() - expect(value).toEqual(1) - expect(valueTwo).toEqual(2) - }) - }) - - it('given no custom injector, when run outside injection context, then throw', () => { - expect(() => injectDummy()).toThrowError( - /injectDummy\(\) can only be used within an injection context/i, - ) - expect(() => injectDummyTwo()).toThrowError( - /injectDummyTwo\(\) can only be used within an injection context/i, - ) - }) - - it('given a custom injector, when run in that injector context without providing number, then throw', () => { - expect(() => injectDummy(Injector.create({ providers: [] }))).toThrowError( - /No provider for InjectionToken/i, - ) - expect(() => - injectDummyTwo(Injector.create({ providers: [] })), - ).toThrowError(/No provider for InjectionToken/i) - }) - - it('given a custom injector, when run in that injector context and providing number, then return value', () => { - const value = injectDummy( - Injector.create({ providers: [{ provide: token, useValue: 2 }] }), - ) - const valueTwo = injectDummyTwo( - Injector.create({ providers: [{ provide: token, useValue: 2 }] }), - ) - expect(value).toEqual(2) - expect(valueTwo).toEqual(3) - }) -}) diff --git a/packages/angular-query-experimental/src/util/assert-injector/assert-injector.ts b/packages/angular-query-experimental/src/util/assert-injector/assert-injector.ts deleted file mode 100644 index 9a18cc442ec..00000000000 --- a/packages/angular-query-experimental/src/util/assert-injector/assert-injector.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* eslint-disable cspell/spellchecker */ -/** - * The code in this file is adapted from NG Extension Platform at https://ngxtension.netlify.app. - * - * Original Author: Chau Tran - * - * NG Extension Platform is an open-source project licensed under the MIT license. - * - * For more information about the original code, see - * https://github.com/nartc/ngxtension-platform - */ -/* eslint-enable */ - -import { - Injector, - assertInInjectionContext, - inject, - runInInjectionContext, -} from '@angular/core' - -/** - * `assertInjector` extends `assertInInjectionContext` with an optional `Injector` - * After assertion, `assertInjector` runs the `runner` function with the guaranteed `Injector` - * whether it is the default `Injector` within the current **Injection Context** - * or the custom `Injector` that was passed in. - * @template {() => any} Runner - Runner is a function that can return anything - * @param fn - the Function to pass in `assertInInjectionContext` - * @param injector - the optional "custom" Injector - * @param runner - the runner fn - * @returns result - returns the result of the Runner - * @example - * ```ts - * function injectValue(injector?: Injector) { - * return assertInjector(injectValue, injector, () => 'value'); - * } - * - * injectValue(); // string - * ``` - */ -export function assertInjector any>( - fn: Function, - injector: Injector | undefined | null, - runner: TRunner, -): ReturnType -/** - * `assertInjector` extends `assertInInjectionContext` with an optional `Injector` - * After assertion, `assertInjector` returns a guaranteed `Injector` whether it is the default `Injector` - * within the current **Injection Context** or the custom `Injector` that was passed in. - * @param fn - the Function to pass in `assertInInjectionContext` - * @param injector - the optional "custom" Injector - * @returns Injector - * @example - * ```ts - * function injectDestroy(injector?: Injector) { - * injector = assertInjector(injectDestroy, injector); - * - * return runInInjectionContext(injector, () => { - * // code - * }) - * } - * ``` - */ -export function assertInjector( - fn: Function, - injector: Injector | undefined | null, -): Injector -/** - * @param fn - the Function to pass in `assertInInjectionContext` - * @param injector - the optional "custom" Injector - * @param runner - the runner fn - * @returns any - */ -export function assertInjector( - fn: Function, - injector: Injector | undefined | null, - runner?: () => any, -) { - !injector && assertInInjectionContext(fn) - const assertedInjector = injector ?? inject(Injector) - - if (!runner) return assertedInjector - return runInInjectionContext(assertedInjector, runner) -}