Skip to content

feat(angular): query resource#10976

Open
eneajaho wants to merge 2 commits into
TanStack:mainfrom
eneajaho:feat/angular-query-resource
Open

feat(angular): query resource#10976
eneajaho wants to merge 2 commits into
TanStack:mainfrom
eneajaho:feat/angular-query-resource

Conversation

@eneajaho

@eneajaho eneajaho commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

🎯 Changes

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • New Features

    • Added queryResource, infiniteQueryResource, and mutationResource APIs that return Angular Resource<T> instances with reactive state and signals for managing queries and mutations.
  • Documentation

    • Added comprehensive guide covering usage of the new resource-shaped APIs.
  • Chores

    • Updated minimum Angular version requirement to 22+ (breaking change for Angular < 22 users).

eneajaho and others added 2 commits June 19, 2026 11:59
…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>
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Introduces three Angular resource-shaped APIs—queryResource, infiniteQueryResource, and mutationResource—backed by TanStack Query observers. Requires Angular 22+ for stable ResourceSnapshot APIs. Refactors createBaseQueryResult and createMutationResult as shared engines for both existing inject* and new resource variants. Bumps Angular peer dependencies from >=16 to >=22.

Changes

Angular Resource-Shaped Query APIs

Layer / File(s) Summary
Shared engine refactors
src/create-base-query.ts, src/inject-mutation.ts
createBaseQueryResult is extracted to return an explicit Signal<QueryObserverResult> with computed() internally, allowing createBaseQuery to simply wrap it. createMutationResult signature changes to accept an explicit Injector so both injectMutation and mutationResource can share it.
Resource type contracts and snapshot converter
src/resource/resource-types.ts, src/resource/to-resource-snapshot.ts
resource-types.ts defines all public interfaces (BaseQueryResource, CreateInfiniteQueryResourceResult, MutationResource) and config/option-function type aliases. to-resource-snapshot.ts adds toResourceSnapshot, mapping TanStack status/fetchStatus/isPlaceholderData to Angular ResourceSnapshot statuses including reloading to preserve cached values during background refetch.
Base query resource engine
src/resource/create-base-query-resource.ts
normalizeQueryResourceArg unifies config-object and options-function inputs into a single reactive thunk, calling queryKey/enabled as functions when provided as zero-arity thunks. createBaseQueryResource injects QueryClient, builds an observer result signal, maps it through resourceFromSnapshots, and exposes computed query state signals plus refetch/reload/set/update actions.
queryResource and infiniteQueryResource
src/query-resource.ts, src/infinite-query-resource.ts
queryResource provides config-object and options-function overloads, normalizes the argument, and delegates to createBaseQueryResource with QueryObserver. infiniteQueryResource mirrors this using InfiniteQueryObserver, adding computed pagination signals (hasNextPage, hasPreviousPage, fetch/error flags) and fetchNextPage/fetchPreviousPage methods.
mutationResource
src/mutation-resource.ts
Internal toMutationSnapshot maps mutation status to Angular ResourceSnapshot. mutationResource wires createMutationResult to an Angular Resource via resourceFromSnapshots, adds computed reactive fields, and exposes mutate/mutateAsync/reset actions running inside untracked.
Public exports and Angular 22 bump
src/index.ts, package.json, .changeset/angular-query-resource.md
index.ts re-exports all new APIs and resource type aliases. package.json bumps Angular devDependencies to ^22.0.0 and peer dependency ranges to >=22.0.0. The changeset documents the new APIs and breaking peer dependency change.
Test suites
src/__tests__/query-resource.test.ts, src/__tests__/infinite-query-resource.test.ts, src/__tests__/mutation-resource.test.ts, src/__tests__/RESOURCE_TESTS_PORTING.md
query-resource.test.ts covers configuration forms, reactive keys, select/placeholderData, imperative actions, error/retry semantics, structural sharing, cancellation, gc, networkMode, and refetchInterval. infinite-query-resource.test.ts tests initial load, sequential page fetching, maxPages capping, and bi-directional pagination. mutation-resource.test.ts tests idle→success, retry, no-retry error, networkMode pause/resume, optimistic rollback, and reset. RESOURCE_TESTS_PORTING.md maps coverage from the ported test suite.
Resources guide docs
docs/config.json, docs/framework/angular/guides/resources.md
Navigation entry added for "Resources" under Angular Guides. The guide documents the Angular 22+ requirement, both config styles for all three APIs, the returned handle surface with value()/data() distinctions, and guidance on choosing between resource APIs and injectQuery.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 Hop hop, a new burrow dug today,
Resources shaped just the Angular way!
queryResource signals fresh from the cache,
Infinite pages load at a dash.
Mutations mutate, then snap back to rest—
This rabbit thinks Angular 22's best! 🌟

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is empty except for the template structure; all required sections including 'Changes', checklist items, and 'Release Impact' are unfilled, providing no substantive information about the implementation. Complete the 'Changes' section describing the motivation and implementation details, check off the completed checklist items, and confirm the changeset has been generated for the breaking dependency change.
Docstring Coverage ⚠️ Warning Docstring coverage is 72.73% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(angular): query resource' clearly identifies the main feature addition (resource-shaped query APIs) and the targeted framework (Angular), accurately reflecting the primary change in the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4f11927 and 648b9fa.

📒 Files selected for processing (17)
  • .changeset/angular-query-resource.md
  • docs/config.json
  • docs/framework/angular/guides/resources.md
  • packages/angular-query-experimental/package.json
  • packages/angular-query-experimental/src/__tests__/RESOURCE_TESTS_PORTING.md
  • packages/angular-query-experimental/src/__tests__/infinite-query-resource.test.ts
  • packages/angular-query-experimental/src/__tests__/mutation-resource.test.ts
  • packages/angular-query-experimental/src/__tests__/query-resource.test.ts
  • packages/angular-query-experimental/src/create-base-query.ts
  • packages/angular-query-experimental/src/index.ts
  • packages/angular-query-experimental/src/infinite-query-resource.ts
  • packages/angular-query-experimental/src/inject-mutation.ts
  • packages/angular-query-experimental/src/mutation-resource.ts
  • packages/angular-query-experimental/src/query-resource.ts
  • packages/angular-query-experimental/src/resource/create-base-query-resource.ts
  • packages/angular-query-experimental/src/resource/resource-types.ts
  • packages/angular-query-experimental/src/resource/to-resource-snapshot.ts

@@ -0,0 +1,11 @@
---
'@tanstack/angular-query-experimental': minor

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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`': major

Also 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.

Comment on lines +157 to +162
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()`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Suggested change
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.

Comment on lines +312 to +314
await q.refetch()
await vi.advanceTimersByTimeAsync(11)
rendered.fixture.detectChanges()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 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.ts

Repository: 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 -50

Repository: 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.ts

Repository: 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`).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resourceFromSnapshots is an experimental API

TQueryKey
>(optionsFn, Observer)

const resourceRef = resourceFromSnapshots<TData | undefined>(() =>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fwiw it's not a ResourceRef

Suggested change
const resourceRef = resourceFromSnapshots<TData | undefined>(() =>
const resource = resourceFromSnapshots<TData | undefined>(() =>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants