feat(angular): query resource#10976
Conversation
…eQueryResource, mutationResource)
Adds resource-shaped counterparts to `injectQuery`, `injectInfiniteQuery`, and
`injectMutation`. Each returns a real Angular `Resource<T>` (value/status/error/
isLoading/hasValue/snapshot) plus the TanStack result signals, and is backed by the
same `QueryClient`, observers and cache as its `inject*` counterpart — so they dedupe
and share data with existing queries.
`queryResource` / `infiniteQueryResource` accept both an ergonomic config object
(reactive `queryKey` / `enabled` thunks) and an options-function (whole-object
reactive, identical to `injectQuery(() => ({ ... }))`).
Implementation reuses the existing observer/subscription machinery by extracting
`createBaseQueryResult` and `createMutationResult` from `createBaseQuery` /
`injectMutation`, then projecting the result signal onto `resourceFromSnapshots`.
Requires Angular >= 22 (uses the stable `resourceFromSnapshots` / `ResourceSnapshot`
APIs); peer dependency raised accordingly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ce-query Ports the resource-surface specs from the angular-resource-query repo onto the new queryResource / infiniteQueryResource / mutationResource APIs, using the adapter's existing fake-timer + render test harness: - query-resource.test.ts: basics, reactive key + dedup with injectQuery, select, placeholderData, set/update/reload, refetch-error data preservation, failureCount, structural sharing, cancellation, gcTime, networkMode, refetchInterval. - infinite-query-resource.test.ts: first page, fetchNextPage, maxPages, bidirectional. - mutation-resource.test.ts: lifecycle, retry, no-retry default, offline pause, optimistic update + rollback, reset. Assertions reflect TanStack core semantics (notably: a background refetch error sets status to 'error' while preserving cached data). RESOURCE_TESTS_PORTING.md maps every source spec to its location here, including the engine specs already covered by query-core's own suite. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
📝 WalkthroughWalkthroughIntroduces three Angular resource-shaped APIs— ChangesAngular Resource-Shaped Query APIs
Sequence Diagram(s)sequenceDiagram
rect rgba(99, 179, 237, 0.5)
Note over Component,Angular Resource: queryResource flow
end
participant Component
participant queryResource
participant createBaseQueryResource
participant createBaseQueryResult
participant QueryObserver
participant toResourceSnapshot
participant Angular Resource
Component->>queryResource: config or optionsFn
queryResource->>queryResource: normalizeQueryResourceArg → () => options
queryResource->>createBaseQueryResource: optionsFn, QueryObserver
createBaseQueryResource->>createBaseQueryResult: optionsFn, QueryObserver
createBaseQueryResult->>QueryObserver: subscribe / observe
QueryObserver-->>createBaseQueryResult: QueryObserverResult Signal
createBaseQueryResult-->>createBaseQueryResource: Signal<QueryObserverResult>
createBaseQueryResource->>toResourceSnapshot: QueryObserverResult
toResourceSnapshot-->>createBaseQueryResource: ResourceSnapshot (idle/loading/resolved/error)
createBaseQueryResource->>Angular Resource: resourceFromSnapshots(snapshots)
Angular Resource-->>createBaseQueryResource: Resource<TData | undefined>
createBaseQueryResource-->>queryResource: BaseQueryResource (+ extras)
queryResource-->>Component: CreateQueryResourceResult
Component->>queryResource: set(newData) / reload()
queryResource->>createBaseQueryResource: setQueryData / refetch
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.changeset/angular-query-resource.md:
- Line 2: The changeset entry for `@tanstack/angular-query-experimental` is marked
as a minor bump, but it introduces a breaking change to the peer dependency
requirement for Angular (dropping support for versions below 22). Change the
bump level from minor to major for the `@tanstack/angular-query-experimental`
entry to correctly reflect this breaking change in the changeset metadata. This
ensures the version bump accurately communicates the breaking nature of the peer
dependency change to consumers.
In `@docs/framework/angular/guides/resources.md`:
- Around line 157-162: Update line 157 to correct the Angular version guidance
for injectQuery since this package now targets Angular >=22 rather than
supporting versions below 22. Additionally, update the example on line 161 that
shows queryResource(['user', 1]) to use the correct documented API form (either
a config object or options-function) instead of the array-based call signature,
ensuring it matches the API patterns documented elsewhere in the PR.
In `@packages/angular-query-experimental/src/__tests__/query-resource.test.ts`:
- Around line 312-314: The deadlock occurs because awaiting the refetch() call
blocks before the fake timers can be advanced, preventing the sleep-based
queryFn from completing. In the query-resource.test.ts file, refactor both
instances (around lines 312-313 and 406-407) by removing the await from the
refetch() call, storing the refetch promise in a variable, then advancing the
fake timers with vi.advanceTimersByTimeAsync(11), and finally awaiting the
stored refetch promise after the timers have been advanced. This allows the
queryFn's sleep to complete while timers are being advanced, avoiding the
deadlock.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e6b4560b-c8a0-49e6-8123-deffec575123
📒 Files selected for processing (17)
.changeset/angular-query-resource.mddocs/config.jsondocs/framework/angular/guides/resources.mdpackages/angular-query-experimental/package.jsonpackages/angular-query-experimental/src/__tests__/RESOURCE_TESTS_PORTING.mdpackages/angular-query-experimental/src/__tests__/infinite-query-resource.test.tspackages/angular-query-experimental/src/__tests__/mutation-resource.test.tspackages/angular-query-experimental/src/__tests__/query-resource.test.tspackages/angular-query-experimental/src/create-base-query.tspackages/angular-query-experimental/src/index.tspackages/angular-query-experimental/src/infinite-query-resource.tspackages/angular-query-experimental/src/inject-mutation.tspackages/angular-query-experimental/src/mutation-resource.tspackages/angular-query-experimental/src/query-resource.tspackages/angular-query-experimental/src/resource/create-base-query-resource.tspackages/angular-query-experimental/src/resource/resource-types.tspackages/angular-query-experimental/src/resource/to-resource-snapshot.ts
| @@ -0,0 +1,11 @@ | |||
| --- | |||
| '@tanstack/angular-query-experimental': minor | |||
There was a problem hiding this comment.
Use a major changeset for the Angular peer-floor break.
This entry documents a breaking install/runtime contract for Angular <22, but the frontmatter is minor. Please align the bump level with the breaking peer dependency change.
Suggested change
-'`@tanstack/angular-query-experimental`': minor
+'`@tanstack/angular-query-experimental`': majorAlso applies to: 11-11
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.changeset/angular-query-resource.md at line 2, The changeset entry for
`@tanstack/angular-query-experimental` is marked as a minor bump, but it
introduces a breaking change to the peer dependency requirement for Angular
(dropping support for versions below 22). Change the bump level from minor to
major for the `@tanstack/angular-query-experimental` entry to correctly reflect
this breaking change in the changeset metadata. This ensures the version bump
accurately communicates the breaking nature of the peer dependency change to
consumers.
| Reach for `injectQuery` when you are targeting an Angular version below 22, or when you prefer the existing flat signal-proxy result shape. | ||
|
|
||
| ## Notes & differences | ||
|
|
||
| - **Shared cache.** `queryResource(['user', 1])` and `injectQuery(() => ({ queryKey: ['user', 1] }))` resolve to the same cached query. | ||
| - **`status` is the resource status.** The TanStack `pending | success | error` status is on `queryStatus()`. |
There was a problem hiding this comment.
Correct incompatible version guidance and the shared-cache call shape.
Line 157 recommends injectQuery for Angular <22, but this package release now targets Angular >=22. Line 161 also shows queryResource(['user', 1]), which does not match the documented API forms in this PR (config object or options-function).
Suggested doc patch
-Reach for `injectQuery` when you are targeting an Angular version below 22, or when you prefer the existing flat signal-proxy result shape.
+Reach for `injectQuery` when you prefer the existing flat signal-proxy result shape.
@@
-- **Shared cache.** `queryResource(['user', 1])` and `injectQuery(() => ({ queryKey: ['user', 1] }))` resolve to the same cached query.
+- **Shared cache.** `queryResource(...)` and `injectQuery(...)` calls that resolve to the same `queryKey` share one cached query.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Reach for `injectQuery` when you are targeting an Angular version below 22, or when you prefer the existing flat signal-proxy result shape. | |
| ## Notes & differences | |
| - **Shared cache.** `queryResource(['user', 1])` and `injectQuery(() => ({ queryKey: ['user', 1] }))` resolve to the same cached query. | |
| - **`status` is the resource status.** The TanStack `pending | success | error` status is on `queryStatus()`. | |
| Reach for `injectQuery` when you prefer the existing flat signal-proxy result shape. | |
| ## Notes & differences | |
| - **Shared cache.** `queryResource(...)` and `injectQuery(...)` calls that resolve to the same `queryKey` share one cached query. | |
| - **`status` is the resource status.** The TanStack `pending | success | error` status is on `queryStatus()`. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/framework/angular/guides/resources.md` around lines 157 - 162, Update
line 157 to correct the Angular version guidance for injectQuery since this
package now targets Angular >=22 rather than supporting versions below 22.
Additionally, update the example on line 161 that shows queryResource(['user',
1]) to use the correct documented API form (either a config object or
options-function) instead of the array-based call signature, ensuring it matches
the API patterns documented elsewhere in the PR.
| await q.refetch() | ||
| await vi.advanceTimersByTimeAsync(11) | ||
| rendered.fixture.detectChanges() |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify ordering pattern that can hang with fake timers.
rg -nP 'await\s+\w+\.refetch\(\)\s*\n\s*await\s+vi\.advanceTimersByTimeAsync' \
packages/angular-query-experimental/src/__tests__/query-resource.test.tsRepository: TanStack/query
Length of output: 40
🏁 Script executed:
# Read the actual file at lines 312-314 and 406-408
cat -n packages/angular-query-experimental/src/__tests__/query-resource.test.ts | sed -n '310,316p'Repository: TanStack/query
Length of output: 343
🏁 Script executed:
# Also check lines 406-408
cat -n packages/angular-query-experimental/src/__tests__/query-resource.test.ts | sed -n '404,410p'Repository: TanStack/query
Length of output: 324
🏁 Script executed:
# Find test function containing line 312
cat -n packages/angular-query-experimental/src/__tests__/query-resource.test.ts | sed -n '280,330p'Repository: TanStack/query
Length of output: 2117
🏁 Script executed:
# Check test setup for fake timers
cat -n packages/angular-query-experimental/src/__tests__/query-resource.test.ts | head -50Repository: TanStack/query
Length of output: 1912
🏁 Script executed:
# Search for all instances of await refetch followed by advanceTimersByTimeAsync
rg -nA1 'await\s+\w+\.refetch\(' packages/angular-query-experimental/src/__tests__/query-resource.test.ts | grep -E '(^[0-9]+|advanceTimersByTimeAsync)'Repository: TanStack/query
Length of output: 210
🏁 Script executed:
# Count total instances to ensure we've identified all occurrences
wc -l packages/angular-query-experimental/src/__tests__/query-resource.test.tsRepository: TanStack/query
Length of output: 135
Avoid awaiting refetch() before advancing fake timers.
These sequences can deadlock under fake timers because the sleep-based queryFn cannot complete until timers are advanced, but the await blocks advancement. Kick off refetch, advance timers, then await the refetch promise.
Two instances found at lines 312–313 and 406–407.
Suggested fix
await q.refetch()
- await vi.advanceTimersByTimeAsync(11)
+ const refetchPromise = q.refetch()
+ await vi.advanceTimersByTimeAsync(11)
+ await refetchPromise
rendered.fixture.detectChanges()🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/angular-query-experimental/src/__tests__/query-resource.test.ts`
around lines 312 - 314, The deadlock occurs because awaiting the refetch() call
blocks before the fake timers can be advanced, preventing the sleep-based
queryFn from completing. In the query-resource.test.ts file, refactor both
instances (around lines 312-313 and 406-407) by removing the await from the
refetch() call, storing the refetch promise in a variable, then advancing the
fake timers with vi.advanceTimersByTimeAsync(11), and finally awaiting the
stored refetch promise after the timers have been advanced. This allows the
queryFn's sleep to complete while timers are being advanced, avoiding the
deadlock.
| title: Resources | ||
| --- | ||
|
|
||
| > IMPORTANT: The resource APIs (`queryResource`, `infiniteQueryResource`, `mutationResource`) require **Angular 22 or newer**, because they are built on the stable [`resource` snapshot APIs](https://angular.dev/guide/signals/resource) (`resourceFromSnapshots`, `ResourceSnapshot`). |
There was a problem hiding this comment.
resourceFromSnapshots is an experimental API
| TQueryKey | ||
| >(optionsFn, Observer) | ||
|
|
||
| const resourceRef = resourceFromSnapshots<TData | undefined>(() => |
There was a problem hiding this comment.
fwiw it's not a ResourceRef
| const resourceRef = resourceFromSnapshots<TData | undefined>(() => | |
| const resource = resourceFromSnapshots<TData | undefined>(() => |
🎯 Changes
✅ Checklist
pnpm run test:pr.🚀 Release Impact
Summary by CodeRabbit
New Features
queryResource,infiniteQueryResource, andmutationResourceAPIs that return AngularResource<T>instances with reactive state and signals for managing queries and mutations.Documentation
Chores