Skip to content

Conversation

@ThiloAschebrock
Copy link

@ThiloAschebrock ThiloAschebrock commented Oct 26, 2025

🎯 Changes

This PR is a follow-up to #9098 and aligns queries with the proposed change for mutations. With this change queries should have a more consistent state as they no longer depend on Angular to sync the state via effects.

Additionally, this change would allow to (optionally?) lazily execute queries in future, i.e., queries would only run if the data is read by some consumer.

✅ 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

  • Refactor
    • Improved query internals for more consistent reactive restoration, persistent observer behavior, proactive result updates, and more reliable pending-fetch tracking and cleanup.
  • Tests
    • Updated test suite to use async timer APIs and direct stability awaits for more reliable, consistent tests.
  • Chores
    • Added a changeset marking a minor version bump.

@changeset-bot
Copy link

changeset-bot bot commented Oct 26, 2025

🦋 Changeset detected

Latest commit: d4f6b87

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@tanstack/angular-query-experimental Minor
@tanstack/angular-query-persist-client Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 26, 2025

Walkthrough

Refactors the base query in @tanstack/angular-query-experimental to stop relying on effect execution, introducing signal-based persistent observer handling, a linkedResultSignal, PendingTaskRef-based fetch tracking, DestroyRef cleanup, and test updates replacing TestBed.tick with Vitest async timer APIs.

Changes

Cohort / File(s) Summary
Changeset
.changeset/little-parks-arrive.md
Adds a changeset documenting a minor version bump and the refactor removing effect-execution dependency.
Tests
packages/angular-query-experimental/src/__tests__/inject-query.test.ts
Replaces synchronous TestBed.tick usage and manual Promise.resolve waits with Vitest async timer APIs (vi.runAllTimersAsync, vi.runOnlyPendingTimersAsync, vi.advanceTimersByTimeAsync) and direct awaits of app.whenStable().
Core Query Logic
packages/angular-query-experimental/src/create-base-query.ts
Reworks base query: uses DestroyRef, replaces isRestoring() with isRestoringSignal(), keeps a persistent observer updated via untracked(), replaces optimistic-outcome signal with a writable linkedResultSignal, integrates PendingTaskRef for fetch tracking, batches notifications, adds cleanup on destroy, and adds a proactive effect to drive execution.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Component
    participant createBaseQuery
    participant Observer
    participant linkedResultSignal
    participant PendingTaskRef
    participant DestroyRef

    Component->>createBaseQuery: instantiate query
    createBaseQuery->>Observer: create persistent observer (untracked updates)
    createBaseQuery->>linkedResultSignal: register proactive effect
    linkedResultSignal->>Observer: trigger evaluation / execution

    Observer->>PendingTaskRef: add pending task (fetching)
    Observer->>linkedResultSignal: update result signal
    PendingTaskRef->>linkedResultSignal: clear when idle

    Component->>DestroyRef: component destroyed
    DestroyRef->>createBaseQuery: invoke cleanup
    createBaseQuery->>Observer: unsubscribe
    createBaseQuery->>PendingTaskRef: clear pending tasks
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Pay special attention to the linkedResultSignal implementation and its writable semantics for Angular <19 compatibility.
  • Verify PendingTaskRef integration and the correctness of adding/clearing pending tasks around observer state transitions.
  • Review the proactive effect and DestroyRef cleanup to ensure no timing or memory-leak regressions.
  • Confirm test changes correctly flush timers and app.whenStable() in all edge cases.

Possibly related PRs

Suggested reviewers

  • arnoud-dv
  • TkDodo

Poem

🐰
I hopped through signals, soft and spry,
LinkedResult hummed without a sigh,
Pending tasks I gently keep,
Cleanup neat before I sleep,
Timers async — hop, leap, reply!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "refactor(angular-query): Remove effects from base query" is directly aligned with the main changes in the changeset. The raw summary confirms that the primary change is a refactor of the base query to "no longer rely on the execution of effects," and the create-base-query.ts changes show removal of effect-based state synchronization in favor of a signal-based approach. The title is concise, uses a clear conventional commit format, and specifically identifies what is being removed (effects) and from where (base query), making it easily understandable for someone scanning the pull request history.
Description Check ✅ Passed The pull request description fully adheres to the repository template structure. All three required sections are present: the 🎯 Changes section provides clear context about the refactor being a follow-up to PR #9098 and explains the motivation around state consistency and future lazy execution; the ✅ Checklist section has both items properly checked, confirming the contributing guide was followed and tests were run locally; and the 🚀 Release Impact section correctly indicates that a changeset was generated for this published code change. The description is complete and not vague, providing meaningful information about the PR's purpose and ensuring all compliance items are addressed.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 274cd5d and d4f6b87.

📒 Files selected for processing (1)
  • .changeset/little-parks-arrive.md (1 hunks)
🔇 Additional comments (1)
.changeset/little-parks-arrive.md (1)

1-5: Changeset looks good.

The format is correct, the version bump (minor) is appropriate for a refactor, and the description accurately captures the nature of the change. The previously flagged typo ("executation" → "execution") appears to have been resolved.


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.

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 38b4008 and 274cd5d.

📒 Files selected for processing (3)
  • .changeset/little-parks-arrive.md (1 hunks)
  • packages/angular-query-experimental/src/__tests__/inject-query.test.ts (3 hunks)
  • packages/angular-query-experimental/src/create-base-query.ts (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/angular-query-experimental/src/create-base-query.ts (4)
packages/angular-query-experimental/src/pending-tasks-compat.ts (2)
  • PENDING_TASKS (9-28)
  • PendingTaskRef (7-7)
packages/angular-query-experimental/src/inject-is-restoring.ts (1)
  • injectIsRestoring (32-36)
packages/query-core/src/utils.ts (1)
  • noop (82-82)
packages/angular-query-experimental/src/signal-proxy.ts (1)
  • signalProxy (14-46)
🔇 Additional comments (9)
packages/angular-query-experimental/src/create-base-query.ts (6)

44-48: LGTM: Proper lifecycle management setup.

The introduction of DestroyRef and the switch to isRestoringSignal() correctly establishes reactive cleanup and restoration state handling.


64-84: LGTM: Efficient persistent observer pattern.

The persistent observer with reactive option updates is a solid improvement. The use of untracked() correctly prevents infinite reactive loops while keeping options synchronized.


97-155: LGTM: Well-implemented signal-based result tracking.

The linkedResultSignal pattern correctly:

  • Manages subscription lifecycle with proper cleanup
  • Handles pending task tracking at both initialization and during state changes
  • Runs subscriptions outside Angular zone for performance
  • Propagates errors through ngZone.onError.emit before throwing

The dual pending task checks (lines 109-111 and 120-127) correctly handle both immediate fetching state and subsequent transitions.


157-157: LGTM: Proper cleanup on destroy.

The destroyRef.onDestroy hook ensures subscriptions and pending tasks are cleaned up when the injection context is destroyed.


159-168: LGTM: Proactive query execution via effect.

This effect ensures queries execute immediately rather than lazily on first read. The comment correctly notes that removing this effect would enable lazy execution, which aligns with the PR objectives mentioning a possible future option for lazy query execution.


170-186: LGTM: Correct result composition with refetch handling.

The double-call pattern linkedResultSignal()() correctly reads the writable signal (as documented in lines 93-95). The refetch method wrapper ensures options are synchronized before execution, which is essential for correct behavior when options have changed.

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

662-677: LGTM: Correct async timer migration.

The test correctly migrates from synchronous TestBed.tick() to asynchronous Vitest timer APIs (vi.runAllTimersAsync()), with proper app.whenStable() awaits to ensure pending tasks complete before assertions.


705-718: LGTM: Appropriate use of runOnlyPendingTimersAsync.

The use of vi.runOnlyPendingTimersAsync() is more precise than runAllTimersAsync() for this test, as it only executes currently pending timers without triggering additional timer callbacks scheduled during execution.


743-757: LGTM: Consistent async timing in invalidation test.

The test correctly awaits timer execution and stability before asserting on query state, both for initial query execution and after invalidation.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant