Skip to content

Conversation

@arnoud-dv
Copy link
Collaborator

@arnoud-dv arnoud-dv commented Sep 19, 2025

Benefits

  • Improved SSR: Server waits for data fetching before rendering
  • Better testing: fixture.whenStable() properly waits for async operations
  • Zoneless support: Correct change detection timing

Summary by CodeRabbit

  • New Features

    • Automatic PendingTasks integration keeps Angular stability checks in sync with in-flight queries and mutations (compatibility for older Angular versions).
    • Refetch now applies the latest options before running, improving consistency.
  • Documentation

    • Zoneless guidance recommending Angular v19+ and explaining PendingTasks synchronization.
  • Tests

    • Expanded integration and unit tests covering query/mutation lifecycles, synchronous flows, HttpClient scenarios, retries, cancellations, and cleanup.
  • Chores

    • Dev dependency updates and ESLint/spell-check configuration adjustments.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 19, 2025

Walkthrough

Adds a PendingTasks compatibility token and integrates PendingTasks into query and mutation lifecycles; queries add/remove pending tasks on fetch and wrap refetch to set options; mutations track isPending to add/clear tasks; extensive tests, docs note, ESLint adjustments, and an rxjs devDependency were added.

Changes

Cohort / File(s) Summary
PendingTasks compatibility token
packages/angular-query-experimental/src/pending-tasks-compat.ts
Adds PENDING_TASKS InjectionToken and PendingTaskRef type; factory probes Angular PendingTasks (compat for <19) and exposes add() delegating to real service or a noop.
Query pending-task integration + refetch wrap
packages/angular-query-experimental/src/create-base-query.ts
Integrates PendingTasks: add/dispose pending task on fetchStatus transitions, dispose on teardown/unsubscribe; wraps returned refetch(...) to call observer.setOptions(defaultedOptionsSignal()) before delegating.
Mutation pending-task integration
packages/angular-query-experimental/src/inject-mutation.ts
Replaces DestroyRef usage with PENDING_TASKS injection; adds pending task on state.isPending, disposes when not pending; uses onCleanup for teardown and observer unsubscribe.
Expanded tests: stability, HTTP, sync, retries, optimistic
packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts, packages/angular-query-experimental/src/__tests__/inject-query.test.ts, packages/angular-query-experimental/src/__tests__/pending-tasks.test.ts
Adds comprehensive tests using ApplicationRef, HttpClient/HttpTestingController, and fake timers covering whenStable(), sync/async queries & mutations, retries, optimistic updates, invalidation, cancellation, concurrency, and cleanup.
Docs (Zoneless note)
docs/framework/angular/zoneless.md
Adds recommendation for Angular v19+ and documents PendingTasks integration aligning ApplicationRef.whenStable() with ongoing queries/mutations.
ESLint (root)
eslint.config.js
Expands cspell dictionary with new terms (e.g., Promisable, TSES, datatag, ɵkind, ɵproviders, refetches).
ESLint (package)
packages/angular-query-experimental/eslint.config.js
Removes cspell/spellchecker rule and updates test rules (disables @typescript-eslint/require-await and jsdoc/require-returns).
Dev dependency
packages/angular-query-experimental/package.json
Adds devDependency: rxjs ^7.8.2.
JSDoc tag removals
packages/angular-query-experimental/src/inject-is-fetching.ts, packages/angular-query-experimental/src/inject-queries.ts
Removes @public JSDoc tags from those APIs; no signature or behavioral changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor C as Component/Test
  participant Q as createBaseQuery
  participant O as QueryObserver
  participant PT as PENDING_TASKS
  participant AR as ApplicationRef

  Note over C,Q: Query initialization and subscription
  C->>Q: createBaseQuery()
  Q->>O: create + subscribe
  O-->>Q: state updates (fetchStatus)

  rect rgb(240,248,255)
  Note right of Q: PendingTasks integration (query)
  Q->>PT: add() when fetchStatus == "fetching"
  PT-->>Q: PendingTaskRef (dispose)
  Q->>PT: dispose() when fetchStatus == "idle"
  end

  Note over C,Q: Refetch wrap
  C->>Q: result.refetch()
  Q->>O: setOptions(defaultedOptionsSignal())
  Q->>O: refetch(...)

  Note over AR: whenStable() waits while tasks pending
  C->>AR: whenStable()
  AR-->>C: resolves after PendingTaskRef disposed
Loading
sequenceDiagram
  autonumber
  actor C as Component/Test
  participant M as injectMutation
  participant MO as MutationObserver
  participant PT as PENDING_TASKS
  participant AR as ApplicationRef

  C->>M: injectMutation()
  M->>MO: observe state

  rect rgb(255,250,240)
  Note right of M: PendingTasks integration (mutation)
  MO-->>M: state.isPending = true
  M->>PT: add()
  PT-->>M: PendingTaskRef
  MO-->>M: state.isPending = false
  M->>PT: dispose()
  end

  Note over C,M: Cleanup
  C-->>M: destroy
  M->>PT: dispose (if active)
  M-->>MO: unsubscribe

  C->>AR: whenStable()
  AR-->>C: resolves after pending disposed
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I thump my paws on zoneless ground,
Pending tasks now hop around;
Queries fetch and refetch sings,
Mutations wait on tiny springs.
Tests and docs and tokens play—
I nibble carrots, code away. 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title Check ✅ Passed The title clearly and concisely states the PR's primary change—integrating Angular v19+ PendingTasks to provide whenStable() support—and directly matches the changes described (pending-tasks compatibility, injector updates, tests, and docs) for the angular-query package. It is specific, on-topic, and readable, so it satisfies the title requirements.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/angular-when-stable

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.

@nx-cloud
Copy link

nx-cloud bot commented Sep 19, 2025

View your CI Pipeline Execution ↗ for commit e2a8d8c

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 1m 43s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 8s View ↗

☁️ Nx Cloud last updated this comment at 2025-09-20 16:10:51 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Sep 19, 2025

More templates

@tanstack/angular-query-experimental

npm i https://pkg.pr.new/@tanstack/angular-query-experimental@9666

@tanstack/eslint-plugin-query

npm i https://pkg.pr.new/@tanstack/eslint-plugin-query@9666

@tanstack/query-async-storage-persister

npm i https://pkg.pr.new/@tanstack/query-async-storage-persister@9666

@tanstack/query-broadcast-client-experimental

npm i https://pkg.pr.new/@tanstack/query-broadcast-client-experimental@9666

@tanstack/query-core

npm i https://pkg.pr.new/@tanstack/query-core@9666

@tanstack/query-devtools

npm i https://pkg.pr.new/@tanstack/query-devtools@9666

@tanstack/query-persist-client-core

npm i https://pkg.pr.new/@tanstack/query-persist-client-core@9666

@tanstack/query-sync-storage-persister

npm i https://pkg.pr.new/@tanstack/query-sync-storage-persister@9666

@tanstack/react-query

npm i https://pkg.pr.new/@tanstack/react-query@9666

@tanstack/react-query-devtools

npm i https://pkg.pr.new/@tanstack/react-query-devtools@9666

@tanstack/react-query-next-experimental

npm i https://pkg.pr.new/@tanstack/react-query-next-experimental@9666

@tanstack/react-query-persist-client

npm i https://pkg.pr.new/@tanstack/react-query-persist-client@9666

@tanstack/solid-query

npm i https://pkg.pr.new/@tanstack/solid-query@9666

@tanstack/solid-query-devtools

npm i https://pkg.pr.new/@tanstack/solid-query-devtools@9666

@tanstack/solid-query-persist-client

npm i https://pkg.pr.new/@tanstack/solid-query-persist-client@9666

@tanstack/svelte-query

npm i https://pkg.pr.new/@tanstack/svelte-query@9666

@tanstack/svelte-query-devtools

npm i https://pkg.pr.new/@tanstack/svelte-query-devtools@9666

@tanstack/svelte-query-persist-client

npm i https://pkg.pr.new/@tanstack/svelte-query-persist-client@9666

@tanstack/vue-query

npm i https://pkg.pr.new/@tanstack/vue-query@9666

@tanstack/vue-query-devtools

npm i https://pkg.pr.new/@tanstack/vue-query-devtools@9666

commit: e2a8d8c

@github-actions
Copy link
Contributor

github-actions bot commented Sep 19, 2025

Sizes for commit e2a8d8c:

Branch Bundle Size
Main
This PR

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (8)
packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts (3)

507-555: Simplify retry test timing to reduce flakiness

You can rely on whenStable() alone after mutate() to avoid multiple tick/advance cycles.

Apply this minimal change:

-      // Synchronize pending effects for each retry attempt
-      TestBed.tick()
-      await Promise.resolve()
-      await vi.advanceTimersByTimeAsync(10)
-
-      TestBed.tick()
-      await Promise.resolve()
-      await vi.advanceTimersByTimeAsync(10)
-
-      TestBed.tick()
-
-      const stablePromise = app.whenStable()
-      await Promise.resolve()
-      await vi.advanceTimersByTimeAsync(10)
-      await stablePromise
+      // Wait for all retries to complete via PendingTasks
+      await app.whenStable()

609-659: Add an assertion for the optimistic state to strengthen coverage

Right after mutate(), verify the optimistic cache write took effect before final resolution.

Apply this addition:

       // Start mutation
       mutation.mutate('test')
 
       // Synchronize pending effects
       TestBed.tick()
 
+      // Optimistic update should be visible immediately
+      expect(queryClient.getQueryData(testQueryKey)).toBe('optimistic: test')
+
       const stablePromise = app.whenStable()

661-695: Misleading test name (no cancellation occurs)

Rename to reflect behavior (successful synchronous mutation) or add a real cancellation step.

Apply either rename:

-    test('should handle synchronous mutation cancellation', async () => {
+    test('should complete synchronous mutation', async () => {

—or add a cancellation (example):

       // Start mutation
       mutation.mutate('test')
 
-      // Synchronize pending effects
+      // Cancel immediately to simulate cancellation path
+      queryClient.cancelMutations({ mutationKey: ['cancel-sync'] })
+
+      // Synchronize pending effects
       TestBed.tick()
 
       const stablePromise = app.whenStable()
@@
-      // Synchronous mutations complete immediately
-      expect(mutation.isSuccess()).toBe(true)
-      expect(mutation.data()).toBe('processed: test')
+      // After cancellation, mutation should not report success
+      expect(mutation.isPending()).toBe(false)
+      expect(mutation.isSuccess()).toBe(false)
packages/angular-query-experimental/src/__tests__/inject-query.test.ts (3)

594-637: HTTP test is well-structured; minor cleanup optional

The scheduled flush works; verify() is correctly called. Consider using httpTestingController.expectOne/flush without setTimeout and relying purely on whenStable to reduce timer coupling.


639-680: Reduce over-synchronization in staleTime flow

You can await whenStable() after refetch() and drop extra microtask/timer steps.

Apply this diff:

-      await query.refetch()
-      await Promise.resolve()
-      await vi.runAllTimersAsync()
-      await app.whenStable()
+      await query.refetch()
+      await app.whenStable()

723-767: Streamline invalidation sync

Prefer awaiting invalidateQueries and then whenStable instead of manual ticks/advances.

Apply this diff:

-      // Invalidate the query
-      queryClient.invalidateQueries({ queryKey: testKey })
-      TestBed.tick()
-
-      // Wait for the invalidation to trigger a refetch
-      await Promise.resolve()
-      await vi.advanceTimersByTimeAsync(10)
-      TestBed.tick()
-
-      await app.whenStable()
+      // Invalidate and wait for PendingTasks to settle
+      await queryClient.invalidateQueries({ queryKey: testKey })
+      await app.whenStable()
packages/angular-query-experimental/src/__tests__/pending-tasks.test.ts (2)

212-276: Offline retry pause behavior validated; consider an explicit check for task count if available

Current assertions are strong. If the compat exposes a public API to read active task count, add it to harden the guarantee that stability remains blocked while paused.

Would you like a follow-up to probe the repository for a readable PendingTasks debug API and add an assertion?


279-334: Component destruction tests: split concerns to remove ambiguity

Both query and mutation are instantiated in TestComponent; in the mutation test, a query is also running. Prefer dedicated components per test to isolate behavior.

Proposed change outline:

  • Create QueryOnlyComponent (query active, no mutation).
  • Create MutationOnlyComponent (mutation active, query disabled or absent).
  • Use each component in its respective test to avoid cross-effects.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed21249 and a9ca29d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • packages/angular-query-experimental/implementation-docs/pending-tasks-integration.md (1 hunks)
  • packages/angular-query-experimental/package.json (1 hunks)
  • packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts (2 hunks)
  • packages/angular-query-experimental/src/__tests__/inject-query.test.ts (4 hunks)
  • packages/angular-query-experimental/src/__tests__/pending-tasks.test.ts (1 hunks)
  • packages/angular-query-experimental/src/create-base-query.ts (4 hunks)
  • packages/angular-query-experimental/src/inject-mutation.ts (4 hunks)
  • packages/angular-query-experimental/src/pending-tasks-compat.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-02T17:57:33.184Z
Learnt from: TkDodo
PR: TanStack/query#9612
File: packages/query-async-storage-persister/src/asyncThrottle.ts:0-0
Timestamp: 2025-09-02T17:57:33.184Z
Learning: When importing from tanstack/query-core in other TanStack Query packages like query-async-storage-persister, a workspace dependency "tanstack/query-core": "workspace:*" needs to be added to the package.json.

Applied to files:

  • packages/angular-query-experimental/src/create-base-query.ts
🧬 Code graph analysis (5)
packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts (2)
packages/angular-query-experimental/src/inject-mutation.ts (1)
  • injectMutation (46-197)
packages/angular-query-experimental/src/providers.ts (1)
  • provideTanStackQuery (105-113)
packages/angular-query-experimental/src/__tests__/pending-tasks.test.ts (1)
packages/angular-query-experimental/src/inject-mutation.ts (1)
  • injectMutation (46-197)
packages/angular-query-experimental/src/create-base-query.ts (2)
packages/angular-query-experimental/src/pending-tasks-compat.ts (2)
  • PENDING_TASKS (9-28)
  • PendingTaskRef (7-7)
packages/angular-query-experimental/src/signal-proxy.ts (1)
  • signalProxy (14-46)
packages/angular-query-experimental/src/inject-mutation.ts (1)
packages/angular-query-experimental/src/pending-tasks-compat.ts (2)
  • PENDING_TASKS (9-28)
  • PendingTaskRef (7-7)
packages/angular-query-experimental/src/__tests__/inject-query.test.ts (2)
packages/angular-query-experimental/src/inject-query.ts (1)
  • injectQuery (218-226)
packages/angular-query-experimental/src/providers.ts (1)
  • provideTanStackQuery (105-113)
🔇 Additional comments (18)
packages/angular-query-experimental/package.json (1)

100-100: LGTM! RxJS dependency addition is appropriate.

The addition of RxJS as a devDependency is appropriate for testing purposes, particularly for testing asynchronous operations and observables in the PendingTasks integration.

packages/angular-query-experimental/src/pending-tasks-compat.ts (1)

1-28: Well-designed compatibility layer for Angular version differences!

The implementation elegantly handles different Angular versions:

  • Correctly detects PendingTasks (v19+) or ExperimentalPendingTasks (v18)
  • Provides a stable no-op fallback for v17 and earlier
  • Uses proper optional injection to handle missing services gracefully

This design ensures backward compatibility while enabling the new functionality where available.

packages/angular-query-experimental/implementation-docs/pending-tasks-integration.md (1)

1-264: Excellent documentation covering all aspects of the PendingTasks integration!

The documentation is comprehensive and well-structured:

  • Clear problem statement and benefits
  • Detailed implementation guidance with code examples
  • Coverage of edge cases (offline, paused queries, destruction)
  • Helpful migration notes and testing patterns
  • Good contributor guidelines

The documentation effectively explains the integration's value for SSR, testing, and zoneless applications.

packages/angular-query-experimental/src/inject-mutation.ts (2)

139-148: Correct PendingTasks tracking for mutations.

The implementation properly:

  • Creates a pending task when isPending becomes true
  • Clears it when the mutation completes
  • Includes appropriate guards against duplicate registrations

163-170: Proper cleanup on component destruction.

Good defensive programming - ensures pending tasks are cleaned up even if the component is destroyed while a mutation is in progress.

packages/angular-query-experimental/src/create-base-query.ts (3)

120-127: Excellent handling of query fetch states with PendingTasks.

The implementation correctly:

  • Registers a pending task when fetching starts
  • Only clears when truly idle (not when paused for offline retries)
  • Guards against duplicate registrations

This ensures whenStable() remains reliable even during network interruptions.


147-153: Robust cleanup on effect teardown.

Properly ensures pending tasks are cleaned up when the effect is destroyed, preventing memory leaks.


167-171: Approve — refetch wrapper maintains query semantics.

It updates observer options from signals, forwards all arguments to the original refetch, and returns its result — preserving parameter/return semantics and the original 'this' binding.

packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts (3)

2-2: Good addition: using ApplicationRef for whenStable coordination

Import is appropriate for PendingTasks-backed stability checks.


471-505: whenStable assertion pattern looks solid

Covers the pending→success transition and data; timing margin (60ms > 50ms) is sensible.


557-607: Concurrent synchronous mutations on same key: LGTM

Nice coverage for same-key, separate observers; assertions are crisp.

packages/angular-query-experimental/src/__tests__/inject-query.test.ts (3)

2-2: Imports for whenStable, HttpClient testing, and lastValueFrom: OK

All required pieces are present for PendingTasks + HttpClient scenarios.

Also applies to: 12-16, 27-27


570-593: Core whenStable query completion: LGTM

Good demonstration that PendingTasks blocks stability until async queryFn resolves.


682-721: Enabled→disabled transition with sync queryFn: LGTM

Covers the PendingTasks gating while disabled and the immediate sync resolution when enabled.

packages/angular-query-experimental/src/__tests__/pending-tasks.test.ts (4)

1-22: New suite provides broad PendingTasks coverage

Great scope: sync/async paths, HTTP, retries, offline pause, destruction, and concurrency.


54-79: Synchronous queryFn + whenStable: LGTM

Demonstrates that even sync producers still require a stability turn before signals update.


156-182: Race during initial subscription handled well

Resolving before first turn and asserting post-whenStable correctness is on point.


385-433: Concurrent mutations: LGTM

Covers different durations and an effectively synchronous path; stability gating behaves as expected.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/angular-query-experimental/src/pending-tasks-compat.ts (1)

1-3: Consider using a more specific import for better tree-shaking.

While the namespace import works, it could impact tree-shaking. Consider importing only what you need from Angular core.

-import { InjectionToken, inject } from '@angular/core'
-import * as ng from '@angular/core'
+import { InjectionToken, inject } from '@angular/core'
 import { noop } from '@tanstack/query-core'
+
+// Access Angular internals for PendingTasks compatibility
+const ng = await import('@angular/core')
packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts (1)

523-523: Consider adding await or removing async from synchronous mutation functions.

The ESLint warnings indicate async functions without await expressions. While these are intentionally synchronous for testing, consider either:

  1. Removing the async keyword if the function body is synchronous
  2. Adding a comment to suppress the warning if async is required for type compatibility

For line 523:

-          mutationFn: async (data: string) => {
+          mutationFn: (data: string) => {

For line 572:

-          mutationFn: async (data: string) => {
+          mutationFn: (data: string) => {

Also applies to: 572-572

packages/angular-query-experimental/src/__tests__/pending-tasks.test.ts (1)

184-184: Consider adding "refetches" to the project dictionary.

The pipeline warning indicates "refetches" is an unknown word, but it's a valid technical term in the context of TanStack Query. Consider adding it to your spell-check dictionary or ignore list.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed21249 and a9ca29d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • packages/angular-query-experimental/implementation-docs/pending-tasks-integration.md (1 hunks)
  • packages/angular-query-experimental/package.json (1 hunks)
  • packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts (2 hunks)
  • packages/angular-query-experimental/src/__tests__/inject-query.test.ts (4 hunks)
  • packages/angular-query-experimental/src/__tests__/pending-tasks.test.ts (1 hunks)
  • packages/angular-query-experimental/src/create-base-query.ts (4 hunks)
  • packages/angular-query-experimental/src/inject-mutation.ts (4 hunks)
  • packages/angular-query-experimental/src/pending-tasks-compat.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-02T17:57:33.184Z
Learnt from: TkDodo
PR: TanStack/query#9612
File: packages/query-async-storage-persister/src/asyncThrottle.ts:0-0
Timestamp: 2025-09-02T17:57:33.184Z
Learning: When importing from tanstack/query-core in other TanStack Query packages like query-async-storage-persister, a workspace dependency "tanstack/query-core": "workspace:*" needs to be added to the package.json.

Applied to files:

  • packages/angular-query-experimental/src/create-base-query.ts
🧬 Code graph analysis (5)
packages/angular-query-experimental/src/__tests__/pending-tasks.test.ts (1)
packages/angular-query-experimental/src/inject-mutation.ts (1)
  • injectMutation (46-197)
packages/angular-query-experimental/src/inject-mutation.ts (2)
packages/angular-query-experimental/src/pending-tasks-compat.ts (2)
  • PENDING_TASKS (9-28)
  • PendingTaskRef (7-7)
packages/query-core/src/mutationObserver.ts (1)
  • state (145-159)
packages/angular-query-experimental/src/__tests__/inject-query.test.ts (2)
packages/angular-query-experimental/src/inject-query.ts (1)
  • injectQuery (218-226)
packages/angular-query-experimental/src/providers.ts (1)
  • provideTanStackQuery (105-113)
packages/angular-query-experimental/src/create-base-query.ts (2)
packages/angular-query-experimental/src/pending-tasks-compat.ts (2)
  • PENDING_TASKS (9-28)
  • PendingTaskRef (7-7)
packages/angular-query-experimental/src/signal-proxy.ts (1)
  • signalProxy (14-46)
packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts (2)
packages/angular-query-experimental/src/inject-mutation.ts (1)
  • injectMutation (46-197)
packages/angular-query-experimental/src/providers.ts (1)
  • provideTanStackQuery (105-113)
🪛 GitHub Actions: pr
packages/angular-query-experimental/src/__tests__/pending-tasks.test.ts

[warning] 184-184: Unknown word: refetches

packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts

[warning] 523-523: Async method 'mutationFn' has no 'await' expression


[warning] 572-572: Async method 'mutationFn' has no 'await' expression

🔇 Additional comments (17)
packages/angular-query-experimental/package.json (1)

100-100: LGTM! RxJS devDependency addition is appropriate.

The addition of RxJS as a devDependency is reasonable for testing purposes, particularly for the lastValueFrom utility used in the HTTP client tests.

packages/angular-query-experimental/src/pending-tasks-compat.ts (1)

13-28: Robust cross-version compatibility implementation.

The compatibility layer elegantly handles the PendingTasks API differences across Angular v17-v19:

  • v19+: PendingTasks
  • v18: ExperimentalPendingTasks
  • v17: graceful no-op fallback

The factory pattern with optional injection ensures the code doesn't break across versions.

packages/angular-query-experimental/src/inject-mutation.ts (2)

132-148: Excellent PendingTasks integration for mutation lifecycle.

The implementation correctly tracks mutation pending states:

  • Registers a pending task when state.isPending becomes true
  • Cancels the task when the mutation completes
  • Properly checks for existing pendingTaskRef to avoid duplicate registrations

This ensures ApplicationRef.whenStable() will wait for in-flight mutations.


163-170: Proper cleanup on component destroy.

Good defensive programming - the cleanup logic ensures any active pending task is canceled when the component is destroyed, preventing memory leaks.

packages/angular-query-experimental/src/__tests__/inject-query.test.ts (3)

570-592: Well-structured test for PendingTasks integration.

The test correctly validates that queries complete before whenStable() resolves, which is crucial for SSR and testing scenarios.


594-637: Comprehensive HttpClient integration test.

Excellent test covering real-world HTTP scenarios with the HttpTestingController. This validates that the PendingTasks integration works correctly with Angular's HTTP client.


639-680: Thorough coverage of synchronous query edge cases.

Good test coverage for synchronous queries with staleTime, ensuring the queryFn isn't called unnecessarily within the stale window.

packages/angular-query-experimental/src/create-base-query.ts (4)

120-127: Well-implemented PendingTasks tracking for query fetch lifecycle.

The implementation correctly:

  • Registers a pending task when fetching starts
  • Cancels it when fetching completes (idle state)
  • Guards against duplicate registrations

This ensures whenStable() waits for active queries to complete.


147-153: Proper cleanup handling for pending tasks.

Good defensive programming with cleanup logic that cancels any active pending task when the effect is cleaned up.


162-172: Smart refetch wrapper to ensure latest options.

The wrapper ensures observer.setOptions is called with current signal values before executing refetch. This prevents stale configuration issues when reactive options change between refetch calls.


116-144: ngZone.runOutsideAngular placement is correct — subscription callbacks re-enter Angular for signal writes and error emission.

The subscription handler calls ngZone.run(...) before resultFromSubscriberSignal.set(...) and ngZone.onError.emit(...), so change detection will run for those updates; returning the unsubscribe directly is acceptable.

packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts (3)

471-505: Excellent test for mutation PendingTasks integration.

The test properly validates that mutations complete before whenStable() resolves, which is essential for SSR scenarios.


507-555: Comprehensive synchronous mutation retry test.

Good coverage of retry behavior with synchronous mutations, ensuring PendingTasks correctly tracks multiple attempts.


609-659: Thorough optimistic update test.

Excellent test validating that optimistic updates work correctly with PendingTasks integration, including proper sequencing of onMutate and onSuccess callbacks.

packages/angular-query-experimental/implementation-docs/pending-tasks-integration.md (1)

1-264: Excellent documentation for the PendingTasks integration!

This is comprehensive documentation that covers all the key aspects of the integration, including compatibility across Angular versions, implementation details, edge cases, and migration guidance. The code examples are clear and practical.

packages/angular-query-experimental/src/__tests__/pending-tasks.test.ts (2)

1-663: Comprehensive test coverage for PendingTasks integration!

The test suite thoroughly covers all critical scenarios including synchronous resolution, race conditions, component destruction, concurrent operations, HttpClient integration, and edge cases. The tests are well-structured and correctly use fake timers to control async behavior.


212-276: PendingTasks correctly remains active while query retry is paused — no change needed.

create-base-query.ts adds a pendingTaskRef when state.fetchStatus === 'fetching' and only clears it when state.fetchStatus === 'idle' (pendingTaskRef logic in packages/angular-query-experimental/src/create-base-query.ts), so the test's assumption that a paused fetch still blocks ApplicationRef.whenStable() is valid.

@arnoud-dv arnoud-dv marked this pull request as draft September 19, 2025 17:42
@arnoud-dv arnoud-dv force-pushed the fix/angular-when-stable branch from a9ca29d to 16eb134 Compare September 20, 2025 12:43
@github-actions github-actions bot added the documentation Improvements or additions to documentation label Sep 20, 2025
@arnoud-dv arnoud-dv force-pushed the fix/angular-when-stable branch 2 times, most recently from e8ca89d to b02a8f4 Compare September 20, 2025 12:46
@arnoud-dv arnoud-dv changed the title fix(angular-query): integrate with v18+ Angular PendingTasks for whenStable() support fix(angular-query): integrate with Angular v18+ PendingTasks for whenStable() support Sep 20, 2025
@codecov
Copy link

codecov bot commented Sep 20, 2025

Codecov Report

❌ Patch coverage is 92.85714% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 46.38%. Comparing base (7d370b9) to head (e2a8d8c).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #9666      +/-   ##
==========================================
+ Coverage   46.19%   46.38%   +0.18%     
==========================================
  Files         213      214       +1     
  Lines        8453     8488      +35     
  Branches     1909     1930      +21     
==========================================
+ Hits         3905     3937      +32     
- Misses       4105     4108       +3     
  Partials      443      443              
Components Coverage Δ
@tanstack/angular-query-experimental 93.85% <92.85%> (-0.23%) ⬇️
@tanstack/eslint-plugin-query 83.24% <ø> (ø)
@tanstack/query-async-storage-persister 43.85% <ø> (ø)
@tanstack/query-broadcast-client-experimental 24.39% <ø> (ø)
@tanstack/query-codemods 0.00% <ø> (ø)
@tanstack/query-core 97.48% <ø> (ø)
@tanstack/query-devtools 3.48% <ø> (ø)
@tanstack/query-persist-client-core 79.60% <ø> (ø)
@tanstack/query-sync-storage-persister 84.61% <ø> (ø)
@tanstack/query-test-utils 77.77% <ø> (ø)
@tanstack/react-query 96.00% <ø> (ø)
@tanstack/react-query-devtools 10.00% <ø> (ø)
@tanstack/react-query-next-experimental ∅ <ø> (∅)
@tanstack/react-query-persist-client 100.00% <ø> (ø)
@tanstack/solid-query 78.06% <ø> (ø)
@tanstack/solid-query-devtools ∅ <ø> (∅)
@tanstack/solid-query-persist-client 100.00% <ø> (ø)
@tanstack/svelte-query 87.58% <ø> (ø)
@tanstack/svelte-query-devtools ∅ <ø> (∅)
@tanstack/svelte-query-persist-client 100.00% <ø> (ø)
@tanstack/vue-query 71.10% <ø> (ø)
@tanstack/vue-query-devtools ∅ <ø> (∅)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@arnoud-dv arnoud-dv force-pushed the fix/angular-when-stable branch from b02a8f4 to 2ebb618 Compare September 20, 2025 12:54
@arnoud-dv arnoud-dv marked this pull request as ready for review September 20, 2025 12:56
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts (1)

417-429: Fix: pass mutateAsync() (a Promise) to expect(...).rejects, not a thunk

-    await expect(() => mutateAsync()).rejects.toThrowError(err)
+    await expect(mutateAsync()).rejects.toThrowError(err)
-    await expect(() => mutateAsync()).rejects.toThrowError(err)
+    await expect(mutateAsync()).rejects.toThrowError(err)
🧹 Nitpick comments (6)
docs/framework/angular/zoneless.md (2)

11-12: Fix MD028: remove blank line inside blockquote.

The blank line between two consecutive blockquoted lines triggers markdownlint (MD028). Remove the empty line to keep them in a single blockquote.

Apply this diff:

-> Besides Zoneless, ZoneJS change detection is also fully supported.
-
-> When using Zoneless, ensure you are on Angular v18 or later to take advantage of the `PendingTasks` integration that keeps `ApplicationRef.whenStable()` in sync with ongoing queries and mutations.
+> Besides Zoneless, ZoneJS change detection is also fully supported.
+> When using Zoneless, ensure you are on Angular v18 or later to take advantage of the `PendingTasks` integration that keeps `ApplicationRef.whenStable()` in sync with ongoing queries and mutations.

12-12: Clarify API references and add links (optional).

Consider mentioning ComponentFixture.whenStable() alongside ApplicationRef.whenStable() (common in tests) and link to Angular docs for PendingTasks and ApplicationRef to aid readers. Both APIs exist in v18+. (angular.dev)

Apply this diff:

-> When using Zoneless, ensure you are on Angular v18 or later to take advantage of the `PendingTasks` integration that keeps `ApplicationRef.whenStable()` in sync with ongoing queries and mutations.
+> When using Zoneless, ensure you are on Angular v18 or later to take advantage of the `PendingTasks` integration that keeps `ApplicationRef.whenStable()` (and `ComponentFixture.whenStable()`) in sync with ongoing queries and mutations. See Angular docs for `PendingTasks` and `ApplicationRef`.

If you prefer inline links:

- ... `PendingTasks` integration that keeps `ApplicationRef.whenStable()` ...
+ ... [`PendingTasks`](https://angular.dev/api/core/PendingTasks) integration that keeps [`ApplicationRef.whenStable()`](https://angular.dev/api/core/ApplicationRef) ...
packages/angular-query-experimental/src/inject-mutation.ts (1)

163-170: Prefer effect cleanup over DestroyRef to avoid subtle lifecycle drift.

If this effect ever re-runs (e.g., future refactor), the destroyRef.onDestroy cleanup won’t fire until parent destroy, risking duplicate subscriptions. Use effect cleanup for symmetry with create-base-query.ts.

Apply:

-  effect(
-    () => {
+  effect(
+    (onCleanup) => {
       // observer.trackResult is not used as this optimization is not needed for Angular
       const observer = observerSignal()
       let pendingTaskRef: PendingTaskRef | null = null

       untracked(() => {
         const unsubscribe = ngZone.runOutsideAngular(() =>
           observer.subscribe(
             notifyManager.batchCalls((state) => {
               ngZone.run(() => {
                 // Track pending task when mutation is pending
                 if (state.isPending && !pendingTaskRef) {
                   pendingTaskRef = pendingTasks.add()
                 }

                 // Clear pending task when mutation is no longer pending
                 if (!state.isPending && pendingTaskRef) {
                   pendingTaskRef()
                   pendingTaskRef = null
                 }
                 …
                 resultFromSubscriberSignal.set(state)
               })
             }),
           ),
         )
-        destroyRef.onDestroy(() => {
+        onCleanup(() => {
           // Clean up any pending task on destroy
           if (pendingTaskRef) {
             pendingTaskRef()
             pendingTaskRef = null
           }
           unsubscribe()
         })
       })
     },
     {
       injector,
     },
   )
packages/angular-query-experimental/src/create-base-query.ts (1)

162-172: Wrap all callable methods, not just refetch.

You update observer options before refetch, but infinite queries also expose fetchNextPage/fetchPreviousPage. Wrap them too to ensure fresh options on call.

-      return {
-        ...result,
-        refetch: (...args: Array<any>) => {
-          // Update options with current signal values before refetch
-          observer.setOptions(defaultedOptionsSignal())
-          return result.refetch(...args)
-        },
-      }
+      const wrap = <F extends (...a: any[]) => any>(fn: F | undefined) =>
+        fn
+          ? ((...args: Parameters<F>): ReturnType<F> => {
+              observer.setOptions(defaultedOptionsSignal())
+              return fn(...args)
+            })
+          : undefined
+
+      return {
+        ...result,
+        refetch: wrap(result.refetch)!,
+        // Present on InfiniteQuery results; undefined on plain Query.
+        fetchNextPage: wrap((result as any).fetchNextPage),
+        fetchPreviousPage: wrap((result as any).fetchPreviousPage),
+      } as typeof result

Please run the infinite query tests to ensure types remain sound and calls still compile.

packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts (1)

661-694: Rename test or implement actual cancellation.

The test titled “synchronous mutation cancellation” never cancels. Either:

  • rename to “should complete synchronous mutation”; or
  • actually cancel/reset mid-flight and assert final state.
packages/angular-query-experimental/src/__tests__/pending-tasks.test.ts (1)

558-588: Cancellation post-start leaves query idle/pending – clarify intent.

Asserting status: 'pending' with fetchStatus: 'idle' is subtle. Consider a brief comment explaining why status remains pending after cancellation to future readers.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a9ca29d and 2ebb618.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (13)
  • docs/framework/angular/zoneless.md (1 hunks)
  • eslint.config.js (1 hunks)
  • packages/angular-query-experimental/eslint.config.js (1 hunks)
  • packages/angular-query-experimental/implementation-docs/pending-tasks-integration.md (1 hunks)
  • packages/angular-query-experimental/package.json (1 hunks)
  • packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts (2 hunks)
  • packages/angular-query-experimental/src/__tests__/inject-query.test.ts (4 hunks)
  • packages/angular-query-experimental/src/__tests__/pending-tasks.test.ts (1 hunks)
  • packages/angular-query-experimental/src/create-base-query.ts (4 hunks)
  • packages/angular-query-experimental/src/inject-is-fetching.ts (0 hunks)
  • packages/angular-query-experimental/src/inject-mutation.ts (4 hunks)
  • packages/angular-query-experimental/src/inject-queries.ts (0 hunks)
  • packages/angular-query-experimental/src/pending-tasks-compat.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • packages/angular-query-experimental/src/inject-is-fetching.ts
  • packages/angular-query-experimental/src/inject-queries.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/angular-query-experimental/eslint.config.js
  • eslint.config.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/angular-query-experimental/package.json
  • packages/angular-query-experimental/src/pending-tasks-compat.ts
  • packages/angular-query-experimental/implementation-docs/pending-tasks-integration.md
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-02T17:57:33.184Z
Learnt from: TkDodo
PR: TanStack/query#9612
File: packages/query-async-storage-persister/src/asyncThrottle.ts:0-0
Timestamp: 2025-09-02T17:57:33.184Z
Learning: When importing from tanstack/query-core in other TanStack Query packages like query-async-storage-persister, a workspace dependency "tanstack/query-core": "workspace:*" needs to be added to the package.json.

Applied to files:

  • packages/angular-query-experimental/src/create-base-query.ts
🧬 Code graph analysis (5)
packages/angular-query-experimental/src/create-base-query.ts (2)
packages/angular-query-experimental/src/pending-tasks-compat.ts (2)
  • PENDING_TASKS (9-28)
  • PendingTaskRef (7-7)
packages/angular-query-experimental/src/signal-proxy.ts (1)
  • signalProxy (14-46)
packages/angular-query-experimental/src/__tests__/inject-query.test.ts (2)
packages/angular-query-experimental/src/inject-query.ts (1)
  • injectQuery (218-226)
packages/angular-query-experimental/src/providers.ts (1)
  • provideTanStackQuery (105-113)
packages/angular-query-experimental/src/__tests__/pending-tasks.test.ts (1)
packages/angular-query-experimental/src/inject-mutation.ts (1)
  • injectMutation (46-197)
packages/angular-query-experimental/src/inject-mutation.ts (2)
packages/angular-query-experimental/src/pending-tasks-compat.ts (2)
  • PENDING_TASKS (9-28)
  • PendingTaskRef (7-7)
packages/query-core/src/mutationObserver.ts (1)
  • state (145-159)
packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts (3)
packages/angular-query-experimental/src/inject-mutation.ts (1)
  • injectMutation (46-197)
packages/angular-query-experimental/src/index.ts (2)
  • injectMutation (37-37)
  • provideTanStackQuery (58-58)
packages/angular-query-experimental/src/providers.ts (1)
  • provideTanStackQuery (105-113)
🪛 markdownlint-cli2 (0.18.1)
docs/framework/angular/zoneless.md

11-11: Blank line inside blockquote

(MD028, no-blanks-blockquote)

🔇 Additional comments (16)
packages/angular-query-experimental/src/inject-mutation.ts (3)

20-21: Good: PendingTasks compat hooks are correctly imported.

The imports match the compat API surface and keep the production bundle lean via type‑only import for the ref.


64-64: Good: Resolve compat service via Injector once.

Fetching PENDING_TASKS here centralizes compat logic and keeps the hot path clean.


132-149: Correct pending-task lifecycle for mutations.

  • Adds exactly one task per in‑flight mutation.
  • Keeps the task open across retries; closes when not pending.
    This aligns with Angular PendingTasks expectations and SSR/test stability.

If you want to double‑check edge cases (rapid re‑mutate, error → retry → success), run the mutation test suite with fake timers enabled.

packages/angular-query-experimental/src/create-base-query.ts (2)

48-49: Good: Compat PendingTasks injection is localized.

Keeps compat boundaries in the Angular adapter and avoids app‑level plumbing.


111-128: Solid: Query PendingTasks lifecycle covers fetching/idle with pause semantics.

  • Starts a task on fetching, holds it through paused (offline), and closes on idle.
  • Cleans up in effect onCleanup.
    Matches the contract validated by tests (e.g., offline pause keeps stability blocked).

Also applies to: 147-153

packages/angular-query-experimental/src/__tests__/inject-query.test.ts (6)

570-592: Nice: Stability gating for async query.

This validates PendingTasks → whenStable() integration in a minimal, non-HTTP scenario.


594-637: Good coverage for HttpClient flow.

Uses HttpTestingController + whenStable() to assert completion, preventing false positives with timers alone.


639-680: Covers synchronous query with staleTime.

Asserts single call within stale window and correctness on subsequent refetch.


682-721: Enabled/disabled transition is exercised correctly.

Confirms initial pending with enabled=false and proper resolution after enabling.


723-767: Invalidate + sync data path looks good.

Verifies refetch after invalidation is reflected before stability resolves.


27-27: Ensure rxjs is available to tests

packages/angular-query-experimental/src/tests/inject-query.test.ts imports lastValueFrom from 'rxjs' — add rxjs to devDependencies of packages/angular-query-experimental/package.json (or to the repo root devDependencies) so tests can run. Sandbox couldn't confirm package.json presence; manually verify rxjs is declared.

packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts (1)

471-505: Good: Stability gating validated for mutations.

Ensures PendingTasks covers mutation lifecycles and whenStable() resolves only after completion.

packages/angular-query-experimental/src/__tests__/pending-tasks.test.ts (4)

54-79: Good: sync query paths covered against whenStable().

Validates that even synchronous producers participate in stability properly.


212-276: Strong offline/paused retry coverage.

Holding the PendingTask through paused ensures SSR/tests don’t move past incomplete work.


300-331: Cleanup on component destruction is verified.

Prevents “stuck” tasks after teardown. Nice regression barrier.


525-554: HTTP cancellation path looks correct.

Asserts error state and verify() cleanup; reduces risk of leaked tasks.

@arnoud-dv arnoud-dv force-pushed the fix/angular-when-stable branch 2 times, most recently from e5d028d to 65df1d8 Compare September 20, 2025 13:20
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
docs/framework/angular/zoneless.md (2)

11-12: Fix MD028 and clarify whenStable targets (ApplicationRef + fixture).

Remove the blank line inside the blockquote to satisfy markdownlint MD028 and mention fixture.whenStable() alongside ApplicationRef.whenStable() to align with the PR intent.

Apply this diff:

-
-> When using Zoneless, ensure you are on Angular v18 or later to take advantage of the `PendingTasks` integration that keeps `ApplicationRef.whenStable()` in sync with ongoing queries and mutations.
+> When using Zoneless, use Angular v18+ to take advantage of `PendingTasks`, which keeps `ApplicationRef.whenStable()` and `fixture.whenStable()` in sync with ongoing queries and mutations.

12-12: Optional: Add brief version nuance/link.

Consider adding a short follow-up sentence to set expectations for earlier Angular versions and link to the new PendingTasks integration doc.

Proposed text to append after this line:

  • “On Angular v17 or earlier, Zoneless still works, but whenStable won’t track queries/mutations via PendingTasks.”
  • Link to your new integration page once it’s published (e.g., “See PendingTasks integration for details.”).
packages/angular-query-experimental/src/inject-mutation.ts (1)

62-68: PendingTasks lifecycle tracking looks correct; consider a small helper to DRY it up.

  • The add/clear logic keyed on state.isPending is sound and cleanup is handled via onCleanup. Nice use of runOutsideAngular/run to minimize zone churn.
  • Minor: this pattern is duplicated with queries. Consider extracting a tiny helper (e.g., trackPendingTask(pendingTasks, () => state.isPending)) to keep behavior consistent across modules.

Please confirm this also behaves as expected when multiple mutation updates occur in quick succession (e.g., retries), i.e., you never accumulate more than one open pending task for a single observer.

Also applies to: 127-169

packages/angular-query-experimental/src/create-base-query.ts (1)

111-128: PendingTasks integration for queries looks correct; clarify paused semantics and consider infinite-query methods.

  • Add/clear on fetchStatus === 'fetching' | 'idle' is reasonable. Should paused be treated as non-pending (current behavior) or pending? Please confirm expected behavior for offline/paused fetches.
  • Optional: apply the same “update options before call” wrapper to infinite-query methods (fetchNextPage, fetchPreviousPage) when present to keep behavior consistent.

Also applies to: 147-153, 156-173

packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts (1)

661-694: Test name is misleading; no cancellation occurs.

Rename to reflect behavior (e.g., “should complete synchronous mutation”). This avoids confusion.

-    test('should handle synchronous mutation cancellation', async () => {
+    test('should complete synchronous mutation', async () => {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2ebb618 and 65df1d8.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (13)
  • docs/framework/angular/zoneless.md (1 hunks)
  • eslint.config.js (1 hunks)
  • packages/angular-query-experimental/eslint.config.js (1 hunks)
  • packages/angular-query-experimental/implementation-docs/pending-tasks-integration.md (1 hunks)
  • packages/angular-query-experimental/package.json (1 hunks)
  • packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts (2 hunks)
  • packages/angular-query-experimental/src/__tests__/inject-query.test.ts (4 hunks)
  • packages/angular-query-experimental/src/__tests__/pending-tasks.test.ts (1 hunks)
  • packages/angular-query-experimental/src/create-base-query.ts (4 hunks)
  • packages/angular-query-experimental/src/inject-is-fetching.ts (0 hunks)
  • packages/angular-query-experimental/src/inject-mutation.ts (4 hunks)
  • packages/angular-query-experimental/src/inject-queries.ts (0 hunks)
  • packages/angular-query-experimental/src/pending-tasks-compat.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • packages/angular-query-experimental/src/inject-is-fetching.ts
  • packages/angular-query-experimental/src/inject-queries.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/angular-query-experimental/eslint.config.js
  • packages/angular-query-experimental/package.json
  • packages/angular-query-experimental/src/tests/pending-tasks.test.ts
  • packages/angular-query-experimental/implementation-docs/pending-tasks-integration.md
  • packages/angular-query-experimental/src/pending-tasks-compat.ts
  • eslint.config.js
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-02T17:57:33.184Z
Learnt from: TkDodo
PR: TanStack/query#9612
File: packages/query-async-storage-persister/src/asyncThrottle.ts:0-0
Timestamp: 2025-09-02T17:57:33.184Z
Learning: When importing from tanstack/query-core in other TanStack Query packages like query-async-storage-persister, a workspace dependency "tanstack/query-core": "workspace:*" needs to be added to the package.json.

Applied to files:

  • packages/angular-query-experimental/src/create-base-query.ts
🧬 Code graph analysis (4)
packages/angular-query-experimental/src/__tests__/inject-query.test.ts (2)
packages/angular-query-experimental/src/inject-query.ts (1)
  • injectQuery (218-226)
packages/angular-query-experimental/src/providers.ts (1)
  • provideTanStackQuery (105-113)
packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts (2)
packages/angular-query-experimental/src/inject-mutation.ts (1)
  • injectMutation (45-195)
packages/angular-query-experimental/src/providers.ts (1)
  • provideTanStackQuery (105-113)
packages/angular-query-experimental/src/inject-mutation.ts (2)
packages/angular-query-experimental/src/pending-tasks-compat.ts (2)
  • PENDING_TASKS (9-32)
  • PendingTaskRef (7-7)
packages/query-core/src/mutationObserver.ts (1)
  • state (145-159)
packages/angular-query-experimental/src/create-base-query.ts (2)
packages/angular-query-experimental/src/pending-tasks-compat.ts (2)
  • PENDING_TASKS (9-32)
  • PendingTaskRef (7-7)
packages/angular-query-experimental/src/signal-proxy.ts (1)
  • signalProxy (14-46)
🪛 markdownlint-cli2 (0.18.1)
docs/framework/angular/zoneless.md

11-11: Blank line inside blockquote

(MD028, no-blanks-blockquote)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test
🔇 Additional comments (6)
packages/angular-query-experimental/src/__tests__/inject-query.test.ts (3)

570-592: Good coverage for whenStable() with async query.

This validates the PendingTasks integration for queries; assertions look solid.

If Angular v17 must remain green, please confirm ApplicationRef.whenStable() exists there under your test setup; otherwise gate/skip this test on older majors.


594-637: HttpClient path test is well structured.

  • Using HttpTestingController + lastValueFrom is the right shape.
  • Timers + whenStable() sequencing should make this reliable.

639-680: Synchronous/stale/enabled/invalidation cases covered nicely.

These scenarios tend to regress with reactive options; the tests are concise and effective.

Also applies to: 682-721, 723-767

packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts (3)

471-505: Mutation whenStable() test LGTM.

Confirms PendingTasks covers mutation flows; assertions are clear.


507-555: Retry path covered well.

Good to see synchronous-throw retries validated alongside whenStable().


609-660: Optimistic-update flow is well asserted.

Covers onMutate/onSuccess plus query cache effects.

@arnoud-dv arnoud-dv force-pushed the fix/angular-when-stable branch 2 times, most recently from c235065 to 37258cf Compare September 20, 2025 16:03
@arnoud-dv arnoud-dv changed the title fix(angular-query): integrate with Angular v18+ PendingTasks for whenStable() support fix(angular-query): integrate with Angular v19+ PendingTasks for whenStable() support Sep 20, 2025
…Stable() support

Integrates TanStack Query for Angular with Angular's PendingTasks API to properly track
asynchronous operations, ensuring ApplicationRef.whenStable() waits for queries and
mutations to complete before resolving.

Features:
- Cross-version compatibility (Angular v16+ with graceful degradation)
- Tracks query fetchStatus and mutation isPending state
- Automatic cleanup on component destruction
- Comprehensive test coverage including edge cases

Benefits:
- Improved SSR: Server waits for data fetching before rendering
- Better testing: fixture.whenStable() properly waits for async operations
- Zoneless support: Correct change detection timing
@arnoud-dv arnoud-dv force-pushed the fix/angular-when-stable branch from 37258cf to e2a8d8c Compare September 20, 2025 16:07
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
docs/framework/angular/zoneless.md (1)

11-12: Fix markdownlint MD028: remove blank line inside the blockquote.

There’s an empty line between two consecutive quoted lines. Remove it to satisfy no-blanks-blockquote.

- 
 > When using Zoneless, ensure you are on Angular v19 or later to take advantage of the `PendingTasks` integration that keeps `ApplicationRef.whenStable()` in sync with ongoing queries and mutations.
packages/angular-query-experimental/src/create-base-query.ts (1)

111-128: PendingTasks lifecycle wiring looks right; consider also covering initial “paused” states.

You add a task on fetching and clear on idle, which keeps tasks active during paused (offline) — exactly what's needed. To also handle offline-first queries that enter paused before an observed fetching, add a guard for paused when no task is present.

-                  if (state.fetchStatus === 'fetching' && !pendingTaskRef) {
+                  // Start tracking when a fetch begins…
+                  if (state.fetchStatus === 'fetching' && !pendingTaskRef) {
                     pendingTaskRef = pendingTasks.add()
                   }
 
+                  // …or if we observe a paused fetch before we ever saw 'fetching' (e.g., offline-first).
+                  if (state.fetchStatus === 'paused' && !pendingTaskRef) {
+                    pendingTaskRef = pendingTasks.add()
+                  }
+
                   if (state.fetchStatus === 'idle' && pendingTaskRef) {
                     pendingTaskRef()
                     pendingTaskRef = null
                   }
packages/angular-query-experimental/src/__tests__/pending-tasks.test.ts (1)

212-277: Optional: add an offline-first test variant.

You cover going offline during retry; consider a case where the app is offline before the initial subscribe so the first state is paused. This ensures the PendingTasks guard holds stability in that path too.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 65df1d8 and e2a8d8c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • docs/framework/angular/zoneless.md (1 hunks)
  • eslint.config.js (1 hunks)
  • packages/angular-query-experimental/eslint.config.js (1 hunks)
  • packages/angular-query-experimental/package.json (1 hunks)
  • packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts (2 hunks)
  • packages/angular-query-experimental/src/__tests__/inject-query.test.ts (4 hunks)
  • packages/angular-query-experimental/src/__tests__/pending-tasks.test.ts (1 hunks)
  • packages/angular-query-experimental/src/create-base-query.ts (4 hunks)
  • packages/angular-query-experimental/src/inject-is-fetching.ts (0 hunks)
  • packages/angular-query-experimental/src/inject-mutation.ts (4 hunks)
  • packages/angular-query-experimental/src/inject-queries.ts (0 hunks)
  • packages/angular-query-experimental/src/pending-tasks-compat.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • packages/angular-query-experimental/src/inject-is-fetching.ts
  • packages/angular-query-experimental/src/inject-queries.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/angular-query-experimental/src/inject-mutation.ts
  • packages/angular-query-experimental/eslint.config.js
  • packages/angular-query-experimental/src/pending-tasks-compat.ts
  • packages/angular-query-experimental/package.json
  • packages/angular-query-experimental/src/tests/inject-mutation.test.ts
  • eslint.config.js
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-02T17:57:33.184Z
Learnt from: TkDodo
PR: TanStack/query#9612
File: packages/query-async-storage-persister/src/asyncThrottle.ts:0-0
Timestamp: 2025-09-02T17:57:33.184Z
Learning: When importing from tanstack/query-core in other TanStack Query packages like query-async-storage-persister, a workspace dependency "tanstack/query-core": "workspace:*" needs to be added to the package.json.

Applied to files:

  • packages/angular-query-experimental/src/create-base-query.ts
🧬 Code graph analysis (3)
packages/angular-query-experimental/src/create-base-query.ts (2)
packages/angular-query-experimental/src/pending-tasks-compat.ts (2)
  • PENDING_TASKS (9-28)
  • PendingTaskRef (7-7)
packages/angular-query-experimental/src/signal-proxy.ts (1)
  • signalProxy (14-46)
packages/angular-query-experimental/src/__tests__/inject-query.test.ts (2)
packages/angular-query-experimental/src/inject-query.ts (1)
  • injectQuery (218-226)
packages/angular-query-experimental/src/providers.ts (1)
  • provideTanStackQuery (105-113)
packages/angular-query-experimental/src/__tests__/pending-tasks.test.ts (1)
packages/angular-query-experimental/src/inject-mutation.ts (1)
  • injectMutation (45-195)
🪛 markdownlint-cli2 (0.18.1)
docs/framework/angular/zoneless.md

11-11: Blank line inside blockquote

(MD028, no-blanks-blockquote)

🔇 Additional comments (12)
packages/angular-query-experimental/src/__tests__/inject-query.test.ts (7)

2-2: Appropriate dependencies added for PendingTasks integration testing.

The new imports (ApplicationRef, HttpClient, provideHttpClient, HttpTestingController, provideHttpClientTesting, and lastValueFrom) are all necessary for the comprehensive testing of the PendingTasks integration functionality being added.

Also applies to: 12-16, 27-27


570-592: Excellent test for basic PendingTasks integration.

This test verifies that queries properly integrate with Angular's PendingTasks system by ensuring whenStable() waits for query completion. The test structure is clean and validates both the initial pending state and final success state.


594-637: Comprehensive HttpClient integration test for PendingTasks.

This test effectively validates that HttpClient-based queries work correctly with the PendingTasks integration. The use of HttpTestingController to simulate HTTP responses and the verification that whenStable() waits for completion demonstrates proper integration.


639-680: Thorough testing of synchronous queryFn with staleTime.

This test covers an important edge case where synchronous queries with staleTime should still work correctly with PendingTasks. The verification of call counts ensures that the refetch behavior is working as expected.


682-721: Good coverage of enabled/disabled query transitions.

This test validates that queries transitioning from disabled to enabled state properly integrate with PendingTasks. The test structure ensures that the query doesn't execute when disabled and properly executes when enabled.


723-767: Effective test for query invalidation scenarios.

This test covers query invalidation with synchronous data, ensuring that the PendingTasks integration works correctly when queries are invalidated and refetched. The call count verification confirms that invalidation triggers the expected refetch.


594-767: PendingTasks integration verified — no action required.

create-base-query calls pendingTasks.add() when fetchStatus becomes "fetching" and clears it when idle; mutations mirror this; the PENDING_TASKS token/factory exists; tests cover these flows (create-base-query.ts, inject-mutation.ts, pending-tasks-compat.ts, tests/pending-tasks.test.ts / inject-query.test.ts).

packages/angular-query-experimental/src/create-base-query.ts (4)

17-18: Good: Compat import is typed and isolated.

Importing the compat token and its ref type keeps Angular <19 builds no-op while enabling v19+. Looks correct.


48-48: Injection site is correct.

inject(PENDING_TASKS) matches the compat factory semantics; safe on all supported Angular versions.


147-153: Cleanup is comprehensive.

You dispose any outstanding task before unsubscribing — prevents leaks and accidental stability locks.


165-172: Refetch wrapper now preserves generics.

The wrapper keeps the original refetch signature via Parameters/ReturnType and updates options pre-call. This resolves the earlier type-widening concern.

packages/angular-query-experimental/src/__tests__/pending-tasks.test.ts (1)

118-118: TestBed.tick() is a valid TestBed API — do not remove these calls.

TestBed.tick() is a TestBed method (it supersedes flushEffects) and synchronizes Angular (change detection/effects); the standalone tick() helper is the fakeAsync-only timer function — they are different. Keep TestBed.tick() here and continue using vi.advanceTimersByTimeAsync / Promise.resolve only to advance timers/microtasks; only replace TestBed.tick() if you target an Angular version that lacks it. (angular.dev)

Likely an incorrect or invalid review comment.

@arnoud-dv arnoud-dv changed the title fix(angular-query): integrate with Angular v19+ PendingTasks for whenStable() support feature(angular-query): integrate with Angular v19+ PendingTasks for whenStable() support Sep 20, 2025
@arnoud-dv arnoud-dv changed the title feature(angular-query): integrate with Angular v19+ PendingTasks for whenStable() support feat(angular-query): integrate with Angular v19+ PendingTasks for whenStable() support Sep 20, 2025
@arnoud-dv arnoud-dv merged commit 3bf9268 into main Sep 20, 2025
8 checks passed
@arnoud-dv arnoud-dv deleted the fix/angular-when-stable branch September 20, 2025 16:46
Hellol77 pushed a commit to Hellol77/query that referenced this pull request Oct 14, 2025
…nStable() support (TanStack#9666)

Integrates TanStack Query for Angular with Angular's PendingTasks API to properly track
asynchronous operations, ensuring ApplicationRef.whenStable() waits for queries and
mutations to complete before resolving.

Features:
- Cross-version compatibility (Angular v16+ with graceful degradation)
- Tracks query fetchStatus and mutation isPending state
- Automatic cleanup on component destruction
- Comprehensive test coverage including edge cases

Benefits:
- Improved SSR: Server waits for data fetching before rendering
- Better testing: fixture.whenStable() properly waits for async operations
- Zoneless support: Correct change detection timing
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation package: angular-query-experimental

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants