UNPKG

@tanstack/angular-query-experimental

Version:

Signals for managing, caching and syncing asynchronous and remote data in Angular

572 lines (552 loc) 17.7 kB
// src/index.ts export * from "@tanstack/query-core"; // src/query-options.ts function queryOptions(options) { return options; } // src/mutation-options.ts function mutationOptions(options) { return options; } // src/infinite-query-options.ts function infiniteQueryOptions(options) { return options; } // src/inject-infinite-query.ts import { InfiniteQueryObserver } from "@tanstack/query-core"; // src/create-base-query.ts import { DestroyRef, NgZone, VERSION, computed as computed2, effect, inject, signal, untracked as untracked2 } from "@angular/core"; import { QueryClient, notifyManager } from "@tanstack/query-core"; // src/signal-proxy.ts import { computed, untracked } from "@angular/core"; function signalProxy(inputSignal) { const internalState = {}; return new Proxy(internalState, { get(target, prop) { const computedField = target[prop]; if (computedField) return computedField; const targetField = untracked(inputSignal)[prop]; if (typeof targetField === "function") return targetField; return target[prop] = computed(() => inputSignal()[prop]); }, has(_, prop) { return !!untracked(inputSignal)[prop]; }, ownKeys() { return Reflect.ownKeys(untracked(inputSignal)); }, getOwnPropertyDescriptor() { return { enumerable: true, configurable: true }; } }); } // src/util/index.ts function shouldThrowError(throwError, params) { if (typeof throwError === "function") { return throwError(...params); } return !!throwError; } function noop() { } // src/create-base-query.ts function createBaseQuery(optionsFn, Observer) { const ngZone = inject(NgZone); const destroyRef = inject(DestroyRef); const queryClient = inject(QueryClient); const defaultedOptionsSignal = computed2(() => { const defaultedOptions = queryClient.defaultQueryOptions(optionsFn()); defaultedOptions._optimisticResults = "optimistic"; return defaultedOptions; }); const observerSignal = (() => { let instance = null; return computed2(() => { return instance ||= new Observer(queryClient, defaultedOptionsSignal()); }); })(); const optimisticResultSignal = computed2( () => observerSignal().getOptimisticResult(defaultedOptionsSignal()) ); const resultFromSubscriberSignal = signal(null); effect( (onCleanup) => { const observer = observerSignal(); const defaultedOptions = defaultedOptionsSignal(); untracked2(() => { observer.setOptions(defaultedOptions); }); onCleanup(() => { ngZone.run(() => resultFromSubscriberSignal.set(null)); }); }, { // Set allowSignalWrites to support Angular < v19 // Set to undefined to avoid warning on newer versions allowSignalWrites: VERSION.major < "19" || void 0 } ); effect(() => { const observer = observerSignal(); untracked2(() => { const unsubscribe = ngZone.runOutsideAngular( () => observer.subscribe( notifyManager.batchCalls((state) => { ngZone.run(() => { if (state.isError && !state.isFetching && // !isRestoring() && // todo: enable when client persistence is implemented shouldThrowError(observer.options.throwOnError, [ state.error, observer.getCurrentQuery() ])) { ngZone.onError.emit(state.error); throw state.error; } resultFromSubscriberSignal.set(state); }); }) ) ); destroyRef.onDestroy(unsubscribe); }); }); return signalProxy( computed2(() => { const subscriberResult = resultFromSubscriberSignal(); const optimisticResult = optimisticResultSignal(); return subscriberResult ?? optimisticResult; }) ); } // src/util/assert-injector/assert-injector.ts import { Injector, assertInInjectionContext, inject as inject2, runInInjectionContext } from "@angular/core"; function assertInjector(fn, injector, runner) { !injector && assertInInjectionContext(fn); const assertedInjector = injector ?? inject2(Injector); if (!runner) return assertedInjector; return runInInjectionContext(assertedInjector, runner); } // src/inject-infinite-query.ts function injectInfiniteQuery(optionsFn, injector) { return assertInjector( injectInfiniteQuery, injector, () => createBaseQuery(optionsFn, InfiniteQueryObserver) ); } // src/inject-is-fetching.ts import { DestroyRef as DestroyRef2, NgZone as NgZone2, inject as inject3, signal as signal2 } from "@angular/core"; import { QueryClient as QueryClient2, notifyManager as notifyManager2 } from "@tanstack/query-core"; function injectIsFetching(filters, injector) { return assertInjector(injectIsFetching, injector, () => { const destroyRef = inject3(DestroyRef2); const ngZone = inject3(NgZone2); const queryClient = inject3(QueryClient2); const cache = queryClient.getQueryCache(); let isFetching = queryClient.isFetching(filters); const result = signal2(isFetching); const unsubscribe = ngZone.runOutsideAngular( () => cache.subscribe( notifyManager2.batchCalls(() => { const newIsFetching = queryClient.isFetching(filters); if (isFetching !== newIsFetching) { isFetching = newIsFetching; ngZone.run(() => { result.set(isFetching); }); } }) ) ); destroyRef.onDestroy(unsubscribe); return result; }); } // src/inject-is-mutating.ts import { DestroyRef as DestroyRef3, NgZone as NgZone3, inject as inject4, signal as signal3 } from "@angular/core"; import { QueryClient as QueryClient3, notifyManager as notifyManager3 } from "@tanstack/query-core"; function injectIsMutating(filters, injector) { return assertInjector(injectIsMutating, injector, () => { const destroyRef = inject4(DestroyRef3); const ngZone = inject4(NgZone3); const queryClient = inject4(QueryClient3); const cache = queryClient.getMutationCache(); let isMutating = queryClient.isMutating(filters); const result = signal3(isMutating); const unsubscribe = ngZone.runOutsideAngular( () => cache.subscribe( notifyManager3.batchCalls(() => { const newIsMutating = queryClient.isMutating(filters); if (isMutating !== newIsMutating) { isMutating = newIsMutating; ngZone.run(() => { result.set(isMutating); }); } }) ) ); destroyRef.onDestroy(unsubscribe); return result; }); } // src/inject-mutation.ts import { DestroyRef as DestroyRef4, NgZone as NgZone4, computed as computed3, effect as effect2, inject as inject5, signal as signal4, untracked as untracked3 } from "@angular/core"; import { MutationObserver, QueryClient as QueryClient4, notifyManager as notifyManager4 } from "@tanstack/query-core"; function injectMutation(optionsFn, injector) { return assertInjector(injectMutation, injector, () => { const destroyRef = inject5(DestroyRef4); const ngZone = inject5(NgZone4); const queryClient = inject5(QueryClient4); const optionsSignal = computed3(optionsFn); const observerSignal = (() => { let instance = null; return computed3(() => { return instance ||= new MutationObserver(queryClient, optionsSignal()); }); })(); const mutateFnSignal = computed3(() => { const observer = observerSignal(); return (variables, mutateOptions) => { observer.mutate(variables, mutateOptions).catch(noop); }; }); const resultFromInitialOptionsSignal = computed3(() => { const observer = observerSignal(); return observer.getCurrentResult(); }); const resultFromSubscriberSignal = signal4(null); effect2( () => { const observer = observerSignal(); const options = optionsSignal(); untracked3(() => { observer.setOptions(options); }); }, { injector } ); effect2( () => { const observer = observerSignal(); untracked3(() => { const unsubscribe = ngZone.runOutsideAngular( () => observer.subscribe( notifyManager4.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 = computed3(() => { const resultFromSubscriber = resultFromSubscriberSignal(); const resultFromInitialOptions = resultFromInitialOptionsSignal(); const result = resultFromSubscriber ?? resultFromInitialOptions; return { ...result, mutate: mutateFnSignal(), mutateAsync: result.mutate }; }); return signalProxy(resultSignal); }); } // src/inject-mutation-state.ts import { DestroyRef as DestroyRef5, NgZone as NgZone5, computed as computed4, inject as inject6, signal as signal5 } from "@angular/core"; import { QueryClient as QueryClient5, notifyManager as notifyManager5, replaceEqualDeep } from "@tanstack/query-core"; function getResult(mutationCache, options) { return mutationCache.findAll(options.filters).map( (mutation) => options.select ? options.select(mutation) : mutation.state ); } function injectMutationState(mutationStateOptionsFn = () => ({}), options) { return assertInjector(injectMutationState, options?.injector, () => { const destroyRef = inject6(DestroyRef5); const ngZone = inject6(NgZone5); const queryClient = inject6(QueryClient5); const mutationCache = queryClient.getMutationCache(); const resultFromOptionsSignal = computed4(() => { return [ getResult(mutationCache, mutationStateOptionsFn()), performance.now() ]; }); const resultFromSubscriberSignal = signal5( null ); const effectiveResultSignal = computed4(() => { const optionsResult = resultFromOptionsSignal(); const subscriberResult = resultFromSubscriberSignal(); return subscriberResult && subscriberResult[1] > optionsResult[1] ? subscriberResult[0] : optionsResult[0]; }); const unsubscribe = ngZone.runOutsideAngular( () => mutationCache.subscribe( notifyManager5.batchCalls(() => { const [lastResult] = effectiveResultSignal(); const nextResult = replaceEqualDeep( lastResult, getResult(mutationCache, mutationStateOptionsFn()) ); if (lastResult !== nextResult) { ngZone.run(() => { resultFromSubscriberSignal.set([nextResult, performance.now()]); }); } }) ) ); destroyRef.onDestroy(unsubscribe); return effectiveResultSignal; }); } // src/inject-queries.ts import { QueriesObserver, QueryClient as QueryClient6, notifyManager as notifyManager6 } from "@tanstack/query-core"; import { DestroyRef as DestroyRef6, NgZone as NgZone6, computed as computed5, effect as effect3, inject as inject7, signal as signal6 } from "@angular/core"; function injectQueries({ queries, ...options }, injector) { return assertInjector(injectQueries, injector, () => { const destroyRef = inject7(DestroyRef6); const ngZone = inject7(NgZone6); const queryClient = inject7(QueryClient6); const defaultedQueries = computed5(() => { return queries().map((opts) => { const defaultedOptions = queryClient.defaultQueryOptions(opts); defaultedOptions._optimisticResults = "optimistic"; return defaultedOptions; }); }); const observer = new QueriesObserver( queryClient, defaultedQueries(), options ); effect3(() => { observer.setQueries( defaultedQueries(), options ); }); const [, getCombinedResult] = observer.getOptimisticResult( defaultedQueries(), options.combine ); const result = signal6(getCombinedResult()); const unsubscribe = ngZone.runOutsideAngular( () => observer.subscribe(notifyManager6.batchCalls(result.set)) ); destroyRef.onDestroy(unsubscribe); return result; }); } // src/inject-query.ts import { QueryObserver } from "@tanstack/query-core"; function injectQuery(optionsFn, injector) { return assertInjector( injectQuery, injector, () => createBaseQuery(optionsFn, QueryObserver) ); } // src/inject-query-client.ts import { Injector as Injector2, inject as inject8 } from "@angular/core"; import { QueryClient as QueryClient7 } from "@tanstack/query-core"; function injectQueryClient(injectOptions = {}) { return (injectOptions.injector ?? inject8(Injector2)).get(QueryClient7); } // src/providers.ts import { DestroyRef as DestroyRef7, ENVIRONMENT_INITIALIZER, PLATFORM_ID, computed as computed6, effect as effect4, inject as inject9, makeEnvironmentProviders } from "@angular/core"; import { QueryClient as QueryClient8, onlineManager } from "@tanstack/query-core"; import { isPlatformBrowser } from "@angular/common"; // src/util/is-dev-mode/is-dev-mode.ts import { isDevMode } from "@angular/core"; // src/providers.ts function provideQueryClient(queryClient) { return { provide: QueryClient8, useValue: queryClient }; } function provideTanStackQuery(queryClient, ...features) { return makeEnvironmentProviders([ provideQueryClient(queryClient), { // Do not use provideEnvironmentInitializer while Angular < v19 is supported provide: ENVIRONMENT_INITIALIZER, multi: true, useValue: () => { queryClient.mount(); inject9(DestroyRef7).onDestroy(() => queryClient.unmount()); } }, features.map((feature) => feature.\u0275providers) ]); } function provideAngularQuery(queryClient) { return provideTanStackQuery(queryClient); } function queryFeature(kind, providers) { return { \u0275kind: kind, \u0275providers: providers }; } function withDevtools(optionsFn) { let providers = []; if (!isDevMode() && !optionsFn) { providers = []; } else { providers = [ { // Do not use provideEnvironmentInitializer while Angular < v19 is supported provide: ENVIRONMENT_INITIALIZER, multi: true, useFactory: () => { if (!isPlatformBrowser(inject9(PLATFORM_ID))) return noop; const injectedClient = inject9(QueryClient8, { optional: true }); const destroyRef = inject9(DestroyRef7); const options = computed6(() => optionsFn?.() ?? {}); let devtools = null; let el = null; const shouldLoadToolsSignal = computed6(() => { const { loadDevtools } = options(); return typeof loadDevtools === "boolean" ? loadDevtools : isDevMode(); }); const getResolvedQueryClient = () => { const client = options().client ?? injectedClient; if (!client) { throw new Error("No QueryClient found"); } return client; }; const destroyDevtools = () => { devtools?.unmount(); el?.remove(); devtools = null; }; return () => effect4(() => { const shouldLoadTools = shouldLoadToolsSignal(); const { client, position, errorTypes, buttonPosition, initialIsOpen } = options(); if (devtools && !shouldLoadTools) { destroyDevtools(); return; } else if (devtools && shouldLoadTools) { client && devtools.setClient(client); position && devtools.setPosition(position); errorTypes && devtools.setErrorTypes(errorTypes); buttonPosition && devtools.setButtonPosition(buttonPosition); initialIsOpen && devtools.setInitialIsOpen(initialIsOpen); return; } else if (!shouldLoadTools) { return; } el = document.body.appendChild(document.createElement("div")); el.classList.add("tsqd-parent-container"); import("@tanstack/query-devtools").then((queryDevtools) => { devtools = new queryDevtools.TanstackQueryDevtools({ ...options(), client: getResolvedQueryClient(), queryFlavor: "Angular Query", version: "5", onlineManager }); el && devtools.mount(el); destroyRef.onDestroy(destroyDevtools); }); }); } } ]; } return queryFeature("DeveloperTools", providers); } var queryFeatures = ["DeveloperTools"]; export { infiniteQueryOptions, injectInfiniteQuery, injectIsFetching, injectIsMutating, injectMutation, injectMutationState, injectQueries, injectQuery, injectQueryClient, mutationOptions, provideAngularQuery, provideQueryClient, provideTanStackQuery, queryFeatures, queryOptions, withDevtools }; //# sourceMappingURL=index.mjs.map