Skip to content

Commit

Permalink
feat(angular-query):
Browse files Browse the repository at this point in the history
- Created isRestoring injection token and provider
- handled restoration phase in create-base-query.ts
- handled restoration phase in inject-queries.ts
  • Loading branch information
OmerGronich committed Nov 23, 2024
1 parent 436422c commit a358520
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 24 deletions.
55 changes: 35 additions & 20 deletions packages/angular-query-experimental/src/create-base-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type {
QueryObserverResult,
} from '@tanstack/query-core'
import type { CreateBaseQueryOptions, CreateBaseQueryResult } from './types'
import { injectIsRestoring } from './inject-is-restoring'

/**
* Base implementation for `injectQuery` and `injectInfiniteQuery`.
Expand All @@ -44,6 +45,7 @@ export function createBaseQuery<
const ngZone = injector.get(NgZone)
const destroyRef = injector.get(DestroyRef)
const queryClient = injector.get(QueryClient)
const isRestoring = injectIsRestoring(injector)

/**
* Signal that has the default options from query client applied
Expand All @@ -54,7 +56,9 @@ export function createBaseQuery<
const defaultedOptionsSignal = computed(() => {
const options = runInInjectionContext(injector, () => optionsFn())
const defaultedOptions = queryClient.defaultQueryOptions(options)
defaultedOptions._optimisticResults = 'optimistic'
defaultedOptions._optimisticResults = isRestoring()
? 'isRestoring'
: 'optimistic'
return defaultedOptions
})

Expand Down Expand Up @@ -87,26 +91,37 @@ export function createBaseQuery<
},
)

// observer.trackResult is not used as this optimization is not needed for Angular
const unsubscribe = observer.subscribe(
notifyManager.batchCalls((state: QueryObserverResult<TData, TError>) => {
ngZone.run(() => {
if (
state.isError &&
!state.isFetching &&
// !isRestoring() && // todo: enable when client persistence is implemented
shouldThrowError(observer.options.throwOnError, [
state.error,
observer.getCurrentQuery(),
])
) {
throw state.error
}
resultSignal.set(state)
})
}),
effect(
() => {
// observer.trackResult is not used as this optimization is not needed for Angular
const unsubscribe = isRestoring()
? () => undefined
: observer.subscribe(
notifyManager.batchCalls(
(state: QueryObserverResult<TData, TError>) => {
ngZone.run(() => {
if (
state.isError &&
!state.isFetching &&
!isRestoring() &&
shouldThrowError(observer.options.throwOnError, [
state.error,
observer.getCurrentQuery(),
])
) {
throw state.error
}
untracked(() => resultSignal.set(state))
})
},
),
)
destroyRef.onDestroy(unsubscribe)
},
{
injector,
},
)
destroyRef.onDestroy(unsubscribe)

return signalProxy(resultSignal) as CreateBaseQueryResult<TData, TError>
})
Expand Down
1 change: 1 addition & 0 deletions packages/angular-query-experimental/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export { infiniteQueryOptions } from './infinite-query-options'
export * from './inject-infinite-query'
export * from './inject-is-fetching'
export * from './inject-is-mutating'
export * from './inject-is-restoring'
export * from './inject-mutation'
export * from './inject-mutation-state'
export * from './inject-queries'
Expand Down
32 changes: 32 additions & 0 deletions packages/angular-query-experimental/src/inject-is-restoring.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { InjectionToken, computed, inject } from '@angular/core'
import { assertInjector } from './util/assert-injector/assert-injector'
import type { Injector, Provider, Signal } from '@angular/core'

const IsRestoring = new InjectionToken<Signal<boolean>>('IsRestoring')

/**
* 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.
* @param injector - The Angular injector to use.
* @returns signal with boolean that indicates whether a restore is in progress.
* @public
*/
export function injectIsRestoring(injector?: Injector): Signal<boolean> {
return assertInjector(
injectIsRestoring,
injector,
() => inject(IsRestoring, { optional: true }) ?? computed(() => false),
)
}

/**
* Used by angular query persist client plugin to provide the signal that tracks the restore state
* @param isRestoring - a readonly signal that returns a boolean
* @returns Provider for the `isRestoring` signal
* @public
*/
export function provideIsRestoring(isRestoring: Signal<boolean>): Provider {
return {
provide: IsRestoring,
useValue: isRestoring,
}
}
25 changes: 21 additions & 4 deletions packages/angular-query-experimental/src/inject-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ import {
QueryClient,
notifyManager,
} from '@tanstack/query-core'
import { DestroyRef, computed, effect, inject, signal } from '@angular/core'
import {
DestroyRef,
computed,
effect,
inject,
signal,
untracked,
} from '@angular/core'
import { assertInjector } from './util/assert-injector/assert-injector'
import type { Injector, Signal } from '@angular/core'
import type {
Expand All @@ -17,6 +24,7 @@ import type {
QueryObserverResult,
ThrowOnError,
} from '@tanstack/query-core'
import { injectIsRestoring } from './inject-is-restoring'

// This defines the `CreateQueryOptions` that are accepted in `QueriesOptions` & `GetOptions`.
// `placeholderData` function does not have a parameter
Expand Down Expand Up @@ -204,12 +212,15 @@ export function injectQueries<
return assertInjector(injectQueries, injector, () => {
const queryClient = inject(QueryClient)
const destroyRef = inject(DestroyRef)
const isRestoring = injectIsRestoring(injector)

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'
defaultedOptions._optimisticResults = isRestoring()
? 'isRestoring'
: 'optimistic'

return defaultedOptions as QueryObserverOptions
})
Expand Down Expand Up @@ -238,8 +249,14 @@ export function injectQueries<

const result = signal(getCombinedResult() as any)

const unsubscribe = observer.subscribe(notifyManager.batchCalls(result.set))
destroyRef.onDestroy(unsubscribe)
effect(() => {
const unsubscribe = isRestoring()
? () => undefined
: observer.subscribe(
notifyManager.batchCalls((v) => untracked(() => result.set(v))),
)
destroyRef.onDestroy(unsubscribe)
})

return result
})
Expand Down

0 comments on commit a358520

Please sign in to comment.