Disabling and Lazy Queries

Purpose and Scope

Disabling and lazy querying are ways to keep TanStack Query declarative while delaying the moment a request is allowed to run. A disabled query is still a query: it has a key, options, cache identity, observers, and result state. What changes is automatic execution. In the public query API, the enabled option can be false or a function of the query, and the result still exposes fields such as status, fetchStatus, isPending, isFetching, isPaused, isEnabled, and refetch. That distinction matters because lazy workflows should usually opt out of fetching only until the application has enough information, not replace query ownership with ad hoc component state.

TanStack Query’s repository describes the library as async state management for fetching, caching, synchronizing, and updating server state across promise-based data sources. The Angular docs in this source set show the same model through injectQuery, where the component declares a queryKey and queryFn, and the adapter exposes signal-like result accessors such as isPending(), isError(), isSuccess(), isFetching(), data(), and error(). A lazy query uses that same shape, but adds a condition that prevents automatic execution until a route parameter, form value, user action, authentication state, or feature gate is ready. Sources: docs/framework/angular/guides/background-fetching-indicators.md, docs/framework/angular/angular-httpclient-and-other-data-fetching-clients.md

Relevant Source Files

  • docs/framework/angular/reference/functions/provideTanStackQuery.md — Defines the Angular provider setup that installs a QueryClient, optional features, and lazy-route InjectionToken patterns used before any query, disabled or otherwise, can be observed.
  • docs/framework/angular/angular-httpclient-and-other-data-fetching-clients.md — Shows that query functions are promise-based and can wrap Angular HttpClient observables with lastValueFrom or firstValueFrom, which is important when a lazy query finally starts fetching.
  • docs/framework/angular/devtools.md — Documents Angular devtools setup through withDevtools, including development-only loading, production subpaths, and reactive lazy loading options that help inspect disabled, fetching, and paused queries.
  • docs/framework/angular/guides/background-fetching-indicators.md — Provides concrete Angular examples for injectQuery, per-query isFetching(), and global injectIsFetching(), which are the indicators readers use after a lazy query becomes active or refetches in the background.
  • docs/config.json — Shows that the documentation site organizes Query content by framework, so this guide should be read as a cross-framework concept with Angular-specific examples in this source slice.
  • docs/community-resources.md — Lists community learning resources and utilities that can complement the official docs when teams need deeper patterns for query-key factories, generated clients, and production conventions.

Core Primitives

The first primitive is the QueryClient. In Angular, provideTanStackQuery sets up the providers necessary to enable TanStack Query functionality and accepts either a QueryClient instance or an InjectionToken that provides one. The same reference explains that the InjectionToken pattern can keep TanStack Query absent from the main application bundle and provide it only on lazy loaded routes or components while still sharing a client. That lazy loading optimization is separate from a disabled query: route-level lazy loading controls when the library enters the bundle, while enabled controls when a declared query is allowed to execute. Sources: docs/framework/angular/reference/functions/provideTanStackQuery.md

The second primitive is the query function. TanStack Query is promise-based and intentionally data-client agnostic, so a lazy query does not care whether the eventual request uses fetch, graphql-request, Angular HttpClient, or another async client, as long as the queryFn returns a promise. The Angular HttpClient guide calls out that observables need conversion through lastValueFrom or firstValueFrom. This is especially relevant for lazy queries because the function may not run during initial component creation; the conversion still belongs inside queryFn, not in a side effect that manually pushes data into local state. Sources: docs/framework/angular/angular-httpclient-and-other-data-fetching-clients.md

The third primitive is the observer result. A query consumer renders from the result state instead of tracking request booleans manually. In the Angular background fetching example, the template branches on todosQuery.isPending(), todosQuery.isError(), and todosQuery.isSuccess(), then shows Refreshing... when todosQuery.isFetching() is true while successful data is already present. For disabled and lazy queries, this state model prevents a common UI bug: the initial non-fetching state, the first user-triggered fetch, and later background refetches should be displayed differently even though they all belong to the same cache entry. Sources: docs/framework/angular/guides/background-fetching-indicators.md

Task Flow: From Disabled to Active

Start by declaring the complete query shape before it is allowed to run. The query key should include the resource and all inputs that define cache identity, such as a selected user id, search text, page number, or filter object. Then gate execution with enabled until those inputs are valid. In React, the public useQuery API documents enabled as a boolean or function that disables automatic running and is also used for dependent queries. The same concept transfers to framework adapters: keep the query declarative, let the adapter subscribe to the cache, and let the condition decide whether automatic fetching should begin.

readonly userQuery = injectQuery(() => ({
  queryKey: ['user', this.userId()],
  queryFn: () => fetchUser(this.userId()),
  enabled: !!this.userId(),
}))

When the condition flips to true, TanStack Query can execute the queryFn, populate the cache, and update observers. If the user later changes the input, the query key changes, giving the new input a distinct cache identity. If the user clears the input, the query can become disabled again without destroying the entire query system. That design is more maintainable than conditionally creating unrelated fetching code paths because invalidation, refetching, devtools inspection, cache reads, and global indicators still speak the language of query keys and observers.

Use refetch for an explicit user action when the query is intentionally lazy rather than merely dependent. For example, a search form can keep a query disabled until the user clicks Search, then call refetch with the current query options. This should be used carefully: if every input change should produce a cache entry and update automatically, prefer enabled tied to input validity plus query-key changes. If the user must make an affirmative action, a disabled query with refetch expresses that workflow while preserving status flags and cache behavior.

Pausing, Network State, and Background Indicators

Disabled and paused are related but not identical. Disabled means the application told the query not to run automatically. Paused means the query wants to fetch but cannot currently proceed, commonly because the configured network mode and online manager consider the app offline. The public result field isPaused exists so UI can distinguish a query waiting on connectivity from a query waiting on user intent. This distinction is useful in offline-aware interfaces: a search form that has not been submitted should not show the same message as a request that is queued until the browser reconnects.

Background indicators should be layered on top of lazy execution rather than replacing it. The Angular guide demonstrates a local isFetching() check that renders Refreshing... after successful data exists and a global injectIsFetching() component that announces when any query is fetching in the background. Once a lazy query has executed at least once, it can behave like any other active cache entry: it may refetch on focus, reconnect, invalidation, or manual refresh depending on its options. Those background transitions should generally use isFetching, while first-load empty states should use pending or loading state. Sources: docs/framework/angular/guides/background-fetching-indicators.md

@if (todosQuery.isPending()) {
  Loading...
} @else if (todosQuery.isSuccess()) {
  @if (todosQuery.isFetching()) {
    Refreshing...
  }
}

Angular Implementation Details

In Angular applications, install the client once with provideTanStackQuery(new QueryClient()) in standalone bootstrap or an NgModule provider list. The provider reference also supports optional features such as withDevtools, and the devtools guide shows the production subpath plus a loadDevtools option for controlling whether tools are loaded automatically, always, or never. For lazy-query work, devtools are particularly helpful because they make it visible whether the query exists, whether it has observers, whether it is fetching, and whether the cache entry is stale or idle. Sources: docs/framework/angular/reference/functions/provideTanStackQuery.md, docs/framework/angular/devtools.md

import { QueryClient, provideTanStackQuery } from '@tanstack/angular-query-experimental'
import { withDevtools } from '@tanstack/angular-query-experimental/devtools'
 
export const appConfig = {
  providers: [provideTanStackQuery(new QueryClient(), withDevtools())],
}

When using Angular HttpClient, keep framework integration inside the queryFn. The Angular guide lists concrete benefits of HttpClient, including testing support, interceptors, Angular dependency injection integration, PendingTasks awareness, and SSR request caching. A disabled or lazy query should not bypass those benefits by moving the request into a click handler that manually assigns component state. Instead, inject HttpClient, convert the observable to a promise, and let enabled or refetch decide when TanStack Query calls the function. Sources: docs/framework/angular/angular-httpclient-and-other-data-fetching-clients.md

Compact Reference

NameRole in disabled or lazy workflowsNotes
enabledPrevents automatic query execution until a boolean or query-derived condition allows itUse for dependent queries, valid-input gates, auth gates, and lazy initial execution
refetchManually starts a disabled or already-created queryBest for explicit user actions such as pressing Search or Retry
fetchStatusDescribes whether the query is fetching, paused, or idleUseful when status alone does not explain network behavior
isPausedIndicates a query that cannot currently continue because execution is pausedDo not confuse with intentionally disabled queries
isPending / isFetchingSeparate first-load waiting from active background fetchingAngular examples expose these as signal-style methods
injectIsFetching()Builds global fetching indicators in AngularCounts background fetching across observed queries
provideTanStackQuery()Installs QueryClient providers for AngularRequired before injectQuery consumers can participate in the cache

The main design rule is to prefer declarative gating over imperative data ownership. If the cache key, query function, and UI observer are known, define the query and disable it until the app is ready. If the whole feature is lazy loaded, use Angular providers or an InjectionToken to control bundle inclusion, then still use query options for request timing. For deeper examples, inspect the framework-specific docs navigation and the community resources page, especially resources around query-key factories and generated clients that help teams standardize lazy and dependent query conventions. Sources: docs/config.json, docs/community-resources.md