diff --git a/packages/angular-query-experimental/etc/angular-query-experimental.api.md b/packages/angular-query-experimental/etc/angular-query-experimental.api.md index 77ad2763f2..cba0f91374 100644 --- a/packages/angular-query-experimental/etc/angular-query-experimental.api.md +++ b/packages/angular-query-experimental/etc/angular-query-experimental.api.md @@ -28,6 +28,7 @@ import type { MutationState } from '@tanstack/query-core'; import type { OmitKeyof } from '@tanstack/query-core'; import type { Override } from '@tanstack/query-core'; import { Provider } from '@angular/core'; +import type { QueriesObserverOptions } from '@tanstack/query-core'; import type { QueriesPlaceholderDataFunction } from '@tanstack/query-core'; import type { QueryClient } from '@tanstack/query-core'; import type { QueryFilters } from '@tanstack/query-core'; @@ -179,10 +180,9 @@ export interface InjectMutationStateOptions { } // @public (undocumented) -export function injectQueries, TCombinedResult = QueriesResults>({ queries, ...options }: { - queries: Signal<[...QueriesOptions]>; - combine?: (result: QueriesResults) => TCombinedResult; -}, injector?: Injector): Signal; +export function injectQueries, TCombinedResult = QueriesResults>({ queriesFn, ...options }: { + queriesFn: (client: QueryClient) => readonly [...QueriesOptions]; +} & QueriesObserverOptions, injector?: Injector): Signal; // @public export function injectQuery(optionsFn: (client: QueryClient) => DefinedInitialDataOptions, injector?: Injector): DefinedCreateQueryResult; @@ -219,32 +219,32 @@ export const provideQueryClient: ((value: QueryClient | (() => QueryClient)) => export function provideTanStackQuery(queryClient: QueryClient, ...features: Array): EnvironmentProviders; // Warning: (ae-forgotten-export) The symbol "MAXIMUM_DEPTH" needs to be exported by the entry point index.d.ts -// Warning: (ae-forgotten-export) The symbol "QueryObserverOptionsForCreateQueries" needs to be exported by the entry point index.d.ts -// Warning: (ae-forgotten-export) The symbol "GetOptions" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "CreateQueryOptionsForInjectQueries" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "GetCreateQueryOptionsForInjectQueries" needs to be exported by the entry point index.d.ts // // @public -export type QueriesOptions, TResult extends Array = [], TDepth extends ReadonlyArray = []> = TDepth['length'] extends MAXIMUM_DEPTH ? Array : T extends [] ? [] : T extends [infer Head] ? [...TResult, GetOptions] : T extends [infer Head, ...infer Tail] ? QueriesOptions<[ -...Tail +export type QueriesOptions, TResults extends Array = [], TDepth extends ReadonlyArray = []> = TDepth['length'] extends MAXIMUM_DEPTH ? Array : T extends [] ? [] : T extends [infer Head] ? [...TResults, GetCreateQueryOptionsForInjectQueries] : T extends [infer Head, ...infer Tails] ? QueriesOptions<[ +...Tails ], [ -...TResult, -GetOptions +...TResults, +GetCreateQueryOptionsForInjectQueries ], [ ...TDepth, 1 -]> : ReadonlyArray extends T ? T : T extends Array> ? Array> : Array; +]> : ReadonlyArray extends T ? T : T extends Array> ? Array> : Array; -// Warning: (ae-forgotten-export) The symbol "GetResults" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "GetCreateQueryResult" needs to be exported by the entry point index.d.ts // // @public -export type QueriesResults, TResult extends Array = [], TDepth extends ReadonlyArray = []> = TDepth['length'] extends MAXIMUM_DEPTH ? Array : T extends [] ? [] : T extends [infer Head] ? [...TResult, GetResults] : T extends [infer Head, ...infer Tail] ? QueriesResults<[ -...Tail +export type QueriesResults, TResults extends Array = [], TDepth extends ReadonlyArray = []> = TDepth['length'] extends MAXIMUM_DEPTH ? Array : T extends [] ? [] : T extends [infer Head] ? [...TResults, GetCreateQueryResult] : T extends [infer Head, ...infer Tails] ? QueriesResults<[ +...Tails ], [ -...TResult, -GetResults +...TResults, +GetCreateQueryResult ], [ ...TDepth, 1 -]> : T extends Array> ? Array> : Array; +]> : T extends Array> ? Array> : Array; // @public (undocumented) export const QUERY_CLIENT: InjectionToken; diff --git a/packages/angular-query-experimental/src/__tests__/inject-queries.test.ts b/packages/angular-query-experimental/src/__tests__/inject-queries.test.ts new file mode 100644 index 0000000000..c9c864b6c1 --- /dev/null +++ b/packages/angular-query-experimental/src/__tests__/inject-queries.test.ts @@ -0,0 +1,194 @@ +import { describe } from 'vitest' +import { TestBed, fakeAsync, flush } from '@angular/core/testing' +import { Component, Injector, input, signal } from '@angular/core' +import { QueryCache, QueryClient, injectQueries, provideAngularQuery } from '..' +import { setSignalInputs, simpleFetcher } from './test-utils' + +describe('injectQueries', () => { + let queryCache: QueryCache + let queryClient: QueryClient + + beforeEach(() => { + queryCache = new QueryCache() + queryClient = new QueryClient({ queryCache }) + TestBed.configureTestingModule({ + providers: [provideAngularQuery(queryClient)], + }) + }) + + test('should return the correct results states', fakeAsync(() => { + const ids = [1, 2, 3] + const query = TestBed.runInInjectionContext(() => { + return injectQueries({ + queriesFn: () => + ids.map((id) => ({ + queryKey: ['post', id], + queryFn: () => + new Promise((resolve) => { + setTimeout(() => { + return resolve(id) + }, 0) + }), + })), + }) + }) + expect(query()[0]?.isPending).toBe(true) + expect(query()[0]?.data).toBe(undefined) + expect(query()[1]?.data).toBe(undefined) + expect(query()[2]?.data).toBe(undefined) + flush() + expect(query()[0]?.isPending).toBe(false) + expect(query()[0]?.data).toBe(1) + expect(query()[1]?.data).toBe(2) + expect(query()[2]?.data).toBe(3) + })) + + test('should return the correct combined results states', fakeAsync(() => { + const ids = [1, 2, 3] + const query = TestBed.runInInjectionContext(() => { + return injectQueries({ + queriesFn: () => + ids.map((id) => ({ + queryKey: ['post2', id], + queryFn: () => + new Promise((resolve) => { + setTimeout(() => { + return resolve(id) + }, 0) + }), + })), + combine: (results) => { + return { + data: results.map((result) => result.data), + pending: results.some((result) => result.isPending), + } + }, + }) + }) + expect(query().data).toStrictEqual([undefined, undefined, undefined]) + expect(query().pending).toBe(true) + flush() + expect(query().data).toStrictEqual([1, 2, 3]) + })) + + test('should update query on options contained signal change', fakeAsync(() => { + const key = signal(['key1', 'key2']) + const spy = vi.fn(simpleFetcher) + + const query = TestBed.runInInjectionContext(() => { + return injectQueries({ + queriesFn: () => [ + { + queryKey: key(), + queryFn: spy, + }, + ], + }) + }) + flush() + expect(spy).toHaveBeenCalledTimes(1) + + expect(query()[0].status).toBe('success') + + key.set(['key3']) + TestBed.flushEffects() + + expect(spy).toHaveBeenCalledTimes(2) + // should call queryFn with context containing the new queryKey + expect(spy).toBeCalledWith({ + meta: undefined, + queryKey: ['key3'], + signal: expect.anything(), + }) + flush() + })) + + test('should only run query once enabled signal is set to true', fakeAsync(() => { + const spy = vi.fn(simpleFetcher) + const enabled = signal(false) + + const query = TestBed.runInInjectionContext(() => { + return injectQueries({ + queriesFn: () => [ + { + queryKey: ['key4'], + queryFn: spy, + enabled: enabled(), + }, + ], + }) + }) + + expect(spy).not.toHaveBeenCalled() + expect(query()[0].status).toBe('pending') + + enabled.set(true) + TestBed.flushEffects() + flush() + expect(spy).toHaveBeenCalledTimes(1) + expect(query()[0].status).toBe('success') + })) + + test('should render with required signal inputs', fakeAsync(() => { + @Component({ + selector: 'app-fake', + template: `{{ query()[0].data }}`, + standalone: true, + }) + class FakeComponent { + name = input.required() + + query = injectQueries({ + queriesFn: () => [ + { + queryKey: ['fake', this.name()], + queryFn: () => Promise.resolve(this.name()), + }, + ], + }) + } + + const fixture = TestBed.createComponent(FakeComponent) + setSignalInputs(fixture.componentInstance, { + name: 'signal-input-required-test', + }) + + flush() + fixture.detectChanges() + + expect(fixture.debugElement.nativeElement.textContent).toEqual( + 'signal-input-required-test', + ) + })) + + describe('injection context', () => { + test('throws NG0203 with descriptive error outside injection context', () => { + expect(() => { + injectQueries({ + queriesFn: () => [ + { + queryKey: ['injectionContextError'], + queryFn: simpleFetcher, + }, + ], + }) + }).toThrowError(/NG0203(.*?)injectQueries/) + }) + + test('can be used outside injection context when passing an injector', () => { + const query = injectQueries( + { + queriesFn: () => [ + { + queryKey: ['injectionContextError'], + queryFn: simpleFetcher, + }, + ], + }, + TestBed.inject(Injector), + ) + + expect(query()[0].status).toBe('pending') + }) + }) +}) diff --git a/packages/angular-query-experimental/src/__tests__/inject-query.test.ts b/packages/angular-query-experimental/src/__tests__/inject-query.test.ts index f9a7dee650..150c84d8e3 100644 --- a/packages/angular-query-experimental/src/__tests__/inject-query.test.ts +++ b/packages/angular-query-experimental/src/__tests__/inject-query.test.ts @@ -506,7 +506,7 @@ describe('injectQuery', () => { expect(query.status()).toBe('error') })) - test('should render with required signal inputs', fakeAsync(async () => { + test('should render with required signal inputs', fakeAsync(() => { @Component({ selector: 'app-fake', template: `{{ query.data() }}`, @@ -534,7 +534,7 @@ describe('injectQuery', () => { ) })) - test('should run optionsFn in injection context', fakeAsync(async () => { + test('should run optionsFn in injection context', fakeAsync(() => { @Injectable() class FakeService { getData(name: string) { @@ -576,7 +576,7 @@ describe('injectQuery', () => { expect(fixture.componentInstance.query.data()).toEqual('test name 2') })) - test('should run optionsFn in injection context and allow passing injector to queryFn', fakeAsync(async () => { + test('should run optionsFn in injection context and allow passing injector to queryFn', fakeAsync(() => { @Injectable() class FakeService { getData(name: string) { diff --git a/packages/angular-query-experimental/src/create-base-query.ts b/packages/angular-query-experimental/src/create-base-query.ts index 7128dc8f8b..d3177477c2 100644 --- a/packages/angular-query-experimental/src/create-base-query.ts +++ b/packages/angular-query-experimental/src/create-base-query.ts @@ -13,11 +13,7 @@ import { QueryClient, notifyManager } from '@tanstack/query-core' import { signalProxy } from './signal-proxy' import { shouldThrowError } from './util' import { lazyInit } from './util/lazy-init/lazy-init' -import type { - QueryKey, - QueryObserver, - QueryObserverResult, -} from '@tanstack/query-core' +import type { QueryKey, QueryObserver } from '@tanstack/query-core' import type { CreateBaseQueryOptions, CreateBaseQueryResult } from './types' /** @@ -89,7 +85,7 @@ export function createBaseQuery< // observer.trackResult is not used as this optimization is not needed for Angular const unsubscribe = observer.subscribe( - notifyManager.batchCalls((state: QueryObserverResult) => { + notifyManager.batchCalls((state) => { ngZone.run(() => { if ( state.isError && diff --git a/packages/angular-query-experimental/src/inject-mutation.ts b/packages/angular-query-experimental/src/inject-mutation.ts index 9320c7afb2..9f5e0e32f2 100644 --- a/packages/angular-query-experimental/src/inject-mutation.ts +++ b/packages/angular-query-experimental/src/inject-mutation.ts @@ -13,14 +13,14 @@ 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 { assertInjector } from './util/assert-injector/assert-injector' import { lazyInit } from './util/lazy-init/lazy-init' -import type { DefaultError, MutationObserverResult } from '@tanstack/query-core' -import type { CreateMutateFunction, CreateMutationResult } from './types' +import type { DefaultError } from '@tanstack/query-core' import type { CreateMutationOptions } from './mutation-options' +import type { CreateMutateFunction, CreateMutationResult } from './types' /** * Injects a mutation: an imperative function that can be invoked which typically performs server side effects. @@ -72,26 +72,17 @@ export function injectMutation< const result = signal(observer.getCurrentResult()) const unsubscribe = observer.subscribe( - notifyManager.batchCalls( - ( - state: MutationObserverResult< - TData, - TError, - TVariables, - TContext - >, - ) => { - ngZone.run(() => { - if ( - state.isError && - shouldThrowError(observer.options.throwOnError, [state.error]) - ) { - throw state.error - } - result.set(state) - }) - }, - ), + notifyManager.batchCalls((state) => { + ngZone.run(() => { + if ( + state.isError && + shouldThrowError(observer.options.throwOnError, [state.error]) + ) { + throw state.error + } + result.set(state) + }) + }), ) destroyRef.onDestroy(unsubscribe) diff --git a/packages/angular-query-experimental/src/inject-queries.ts b/packages/angular-query-experimental/src/inject-queries.ts index 423d81c29e..2749a25b1c 100644 --- a/packages/angular-query-experimental/src/inject-queries.ts +++ b/packages/angular-query-experimental/src/inject-queries.ts @@ -1,13 +1,25 @@ +import { + DestroyRef, + Injector, + NgZone, + computed, + effect, + inject, + runInInjectionContext, + signal, + untracked, +} from '@angular/core' import { QueriesObserver, QueryClient, notifyManager, } from '@tanstack/query-core' -import { DestroyRef, computed, effect, inject, signal } from '@angular/core' import { assertInjector } from './util/assert-injector/assert-injector' -import type { Injector, Signal } from '@angular/core' +import { lazySignalInitializer } from './util/lazy-signal-initializer/lazy-signal-initializer' +import type { CreateQueryOptions } from './types' import type { DefaultError, + DefinedQueryObserverResult, OmitKeyof, QueriesObserverOptions, QueriesPlaceholderDataFunction, @@ -17,16 +29,17 @@ import type { QueryObserverResult, ThrowOnError, } from '@tanstack/query-core' +import type { Signal } from '@angular/core' // This defines the `CreateQueryOptions` that are accepted in `QueriesOptions` & `GetOptions`. // `placeholderData` function does not have a parameter -type QueryObserverOptionsForCreateQueries< +type CreateQueryOptionsForInjectQueries< TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, > = OmitKeyof< - QueryObserverOptions, + CreateQueryOptions, 'placeholderData' > & { placeholderData?: TQueryFnData | QueriesPlaceholderDataFunction @@ -36,68 +49,86 @@ type QueryObserverOptionsForCreateQueries< type MAXIMUM_DEPTH = 20 // Widen the type of the symbol to enable type inference even if skipToken is not immutable. -type SkipTokenForUseQueries = symbol +type SkipTokenForInjectQueries = symbol -type GetOptions = +type GetCreateQueryOptionsForInjectQueries = // Part 1: responsible for applying explicit type parameter to function arguments, if object { queryFnData: TQueryFnData, error: TError, data: TData } T extends { queryFnData: infer TQueryFnData error?: infer TError data: infer TData } - ? QueryObserverOptionsForCreateQueries + ? CreateQueryOptionsForInjectQueries : T extends { queryFnData: infer TQueryFnData; error?: infer TError } - ? QueryObserverOptionsForCreateQueries + ? CreateQueryOptionsForInjectQueries : T extends { data: infer TData; error?: infer TError } - ? QueryObserverOptionsForCreateQueries + ? CreateQueryOptionsForInjectQueries : // Part 2: responsible for applying explicit type parameter to function arguments, if tuple [TQueryFnData, TError, TData] T extends [infer TQueryFnData, infer TError, infer TData] - ? QueryObserverOptionsForCreateQueries + ? CreateQueryOptionsForInjectQueries : T extends [infer TQueryFnData, infer TError] - ? QueryObserverOptionsForCreateQueries + ? CreateQueryOptionsForInjectQueries : T extends [infer TQueryFnData] - ? QueryObserverOptionsForCreateQueries + ? CreateQueryOptionsForInjectQueries : // Part 3: responsible for inferring and enforcing type if no explicit parameter was provided T extends { queryFn?: | QueryFunction - | SkipTokenForUseQueries - select: (data: any) => infer TData + | SkipTokenForInjectQueries + select?: (data: any) => infer TData throwOnError?: ThrowOnError } - ? QueryObserverOptionsForCreateQueries< + ? CreateQueryOptionsForInjectQueries< TQueryFnData, unknown extends TError ? DefaultError : TError, unknown extends TData ? TQueryFnData : TData, TQueryKey > : // Fallback - QueryObserverOptionsForCreateQueries + CreateQueryOptionsForInjectQueries + +// A defined initialData setting should return a DefinedQueryObserverResult rather than QueryObserverResult +type GetDefinedOrUndefinedQueryResult = T extends { + initialData?: infer TInitialData +} + ? unknown extends TInitialData + ? QueryObserverResult + : TInitialData extends TData + ? DefinedQueryObserverResult + : TInitialData extends () => infer TInitialDataResult + ? unknown extends TInitialDataResult + ? QueryObserverResult + : TInitialDataResult extends TData + ? DefinedQueryObserverResult + : QueryObserverResult + : QueryObserverResult + : QueryObserverResult -type GetResults = +type GetCreateQueryResult = // Part 1: responsible for mapping explicit type parameter to function result, if object T extends { queryFnData: any; error?: infer TError; data: infer TData } - ? QueryObserverResult + ? GetDefinedOrUndefinedQueryResult : T extends { queryFnData: infer TQueryFnData; error?: infer TError } - ? QueryObserverResult + ? GetDefinedOrUndefinedQueryResult : T extends { data: infer TData; error?: infer TError } - ? QueryObserverResult + ? GetDefinedOrUndefinedQueryResult : // Part 2: responsible for mapping explicit type parameter to function result, if tuple T extends [any, infer TError, infer TData] - ? QueryObserverResult + ? GetDefinedOrUndefinedQueryResult : T extends [infer TQueryFnData, infer TError] - ? QueryObserverResult + ? GetDefinedOrUndefinedQueryResult : T extends [infer TQueryFnData] - ? QueryObserverResult + ? GetDefinedOrUndefinedQueryResult : // Part 3: responsible for mapping inferred type to results, if no explicit parameter was provided T extends { queryFn?: | QueryFunction - | SkipTokenForUseQueries - select: (data: any) => infer TData + | SkipTokenForInjectQueries + select?: (data: any) => infer TData throwOnError?: ThrowOnError } - ? QueryObserverResult< + ? GetDefinedOrUndefinedQueryResult< + T, unknown extends TData ? TQueryFnData : TData, unknown extends TError ? DefaultError : TError > @@ -110,18 +141,18 @@ type GetResults = */ export type QueriesOptions< T extends Array, - TResult extends Array = [], + TResults extends Array = [], TDepth extends ReadonlyArray = [], > = TDepth['length'] extends MAXIMUM_DEPTH - ? Array + ? Array : T extends [] ? [] : T extends [infer Head] - ? [...TResult, GetOptions] - : T extends [infer Head, ...infer Tail] + ? [...TResults, GetCreateQueryOptionsForInjectQueries] + : T extends [infer Head, ...infer Tails] ? QueriesOptions< - [...Tail], - [...TResult, GetOptions], + [...Tails], + [...TResults, GetCreateQueryOptionsForInjectQueries], [...TDepth, 1] > : ReadonlyArray extends T @@ -129,7 +160,7 @@ export type QueriesOptions< : // If T is *some* array but we couldn't assign unknown[] to it, then it must hold some known/homogenous type! // use this to infer the param types in the case of Array.map() argument T extends Array< - QueryObserverOptionsForCreateQueries< + CreateQueryOptionsForInjectQueries< infer TQueryFnData, infer TError, infer TData, @@ -137,7 +168,7 @@ export type QueriesOptions< > > ? Array< - QueryObserverOptionsForCreateQueries< + CreateQueryOptionsForInjectQueries< TQueryFnData, TError, TData, @@ -145,7 +176,7 @@ export type QueriesOptions< > > : // Fallback - Array + Array /** * QueriesResults reducer recursively maps type param to results @@ -153,29 +184,29 @@ export type QueriesOptions< */ export type QueriesResults< T extends Array, - TResult extends Array = [], + TResults extends Array = [], TDepth extends ReadonlyArray = [], > = TDepth['length'] extends MAXIMUM_DEPTH ? Array : T extends [] ? [] : T extends [infer Head] - ? [...TResult, GetResults] - : T extends [infer Head, ...infer Tail] + ? [...TResults, GetCreateQueryResult] + : T extends [infer Head, ...infer Tails] ? QueriesResults< - [...Tail], - [...TResult, GetResults], + [...Tails], + [...TResults, GetCreateQueryResult], [...TDepth, 1] > : T extends Array< - QueryObserverOptionsForCreateQueries< + CreateQueryOptionsForInjectQueries< infer TQueryFnData, infer TError, infer TData, any > > - ? // Dynamic-size (homogenous) CreateQueryOptions array: map directly to array of results + ? // Dynamic-size (homogenous) UseQueryOptions array: map directly to array of results Array< QueryObserverResult< unknown extends TData ? TQueryFnData : TData, @@ -193,54 +224,91 @@ export function injectQueries< TCombinedResult = QueriesResults, >( { - queries, + queriesFn, ...options }: { - queries: Signal<[...QueriesOptions]> - combine?: (result: QueriesResults) => TCombinedResult - }, + queriesFn: (client: QueryClient) => readonly [...QueriesOptions] + } & QueriesObserverOptions, injector?: Injector, ): Signal { return assertInjector(injectQueries, injector, () => { - const queryClient = inject(QueryClient) - const destroyRef = inject(DestroyRef) + const currentInjector = inject(Injector) + const ngZone = currentInjector.get(NgZone) + const destroyRef = currentInjector.get(DestroyRef) + const queryClient = currentInjector.get(QueryClient) + + return lazySignalInitializer(() => { + const defaultedQueriesOptionsSignal = computed(() => { + const queriesOptions = runInInjectionContext(currentInjector, () => + queriesFn(queryClient), + ) - const defaultedQueries = computed(() => { - return queries().map((opts) => { - const defaultedOptions = queryClient.defaultQueryOptions(opts) - // Make sure the results are already in fetching state before subscribing or updating options - defaultedOptions._optimisticResults = 'optimistic' + return queriesOptions.map((opts) => { + const defaultedOptions = queryClient.defaultQueryOptions(opts) + // Make sure the results are already in fetching state before subscribing or updating options + defaultedOptions._optimisticResults = 'optimistic' - return defaultedOptions as QueryObserverOptions + return defaultedOptions as QueryObserverOptions + }) }) - }) - const observer = new QueriesObserver( - queryClient, - defaultedQueries(), - options as QueriesObserverOptions, - ) - - // Do not notify on updates because of changes in the options because - // these changes should already be reflected in the optimistic result. - effect(() => { - observer.setQueries( - defaultedQueries(), - options as QueriesObserverOptions, - { listeners: false }, + const observer = new QueriesObserver( + queryClient, + defaultedQueriesOptionsSignal(), + options, + ) + + const resultSignal = signal( + observer.getOptimisticResult( + defaultedQueriesOptionsSignal(), + options.combine, + )[1](), ) - }) - const [, getCombinedResult] = observer.getOptimisticResult( - defaultedQueries(), - (options as QueriesObserverOptions).combine, - ) + effect( + () => { + const defaultedQueriesOptions = defaultedQueriesOptionsSignal() + observer.setQueries(defaultedQueriesOptions, options, { + // Do not notify on updates because of changes in the options because + // these changes should already be reflected in the optimistic result. + listeners: false, + }) - const result = signal(getCombinedResult() as any) + untracked(() => { + resultSignal.set( + observer.getOptimisticResult( + defaultedQueriesOptionsSignal(), + options.combine, + )[1](), + ) + }) + }, + { + injector: currentInjector, + }, + ) + + const unsubscribe = observer.subscribe( + notifyManager.batchCalls((state) => { + ngZone.run(() => { + for (const result of state) { + if (result.isError && !result.isFetching) { + throw result.error + } + } - const unsubscribe = observer.subscribe(notifyManager.batchCalls(result.set)) - destroyRef.onDestroy(unsubscribe) + resultSignal.set( + observer.getOptimisticResult( + defaultedQueriesOptionsSignal(), + options.combine, + )[1](observer.getCurrentResult()), + ) + }) + }), + ) + destroyRef.onDestroy(unsubscribe) - return result + return resultSignal.asReadonly() + }) }) }