Parallel and Dependent Queries
Purpose and Scope
Parallel and dependent queries are two ways to coordinate multiple pieces of server state without manually synchronizing request state in component effects. A parallel query set is a group of independent queries that can run at the same time because none of them needs data from the others. A dependent query chain is a sequence where a later query is intentionally disabled until an earlier query has produced the input it needs. TanStack Query treats both patterns as declarative cache subscriptions: each query declares a key, a promise-returning function, and any gating conditions, while the QueryClient coordinates fetching, caching, observers, and background updates.
The official Query positioning describes Query as a server-state manager that gives asynchronous data a cache, lifecycle, and declarative APIs for fetching, sharing, refetching, mutating, and observing server state. That framing matters for this topic because parallelism is not primarily about calling Promise.all in a component; it is about letting each resource own its cache identity and lifecycle. Dependent queries follow the same principle. Instead of nesting fetches and copying remote data into local state, derive the second query key and its enabled condition from the first query result, so cache reads, retries, invalidation, and devtools remain query-aware.
Sources: docs/framework/angular/reference/functions/provideTanStackQuery.md, docs/framework/angular/angular-httpclient-and-other-data-fetching-clients.md, docs/framework/angular/guides/background-fetching-indicators.md
Core Primitives
The shared primitive behind both workflows is the QueryClient. In Angular, the application enables TanStack Query by providing a QueryClient with provideTanStackQuery, which returns Angular providers and can also accept optional features such as devtools. This mirrors the framework-adapter model across the repository: the adapter exposes framework-native APIs, but the cache and query lifecycle are owned by TanStack Query. For Angular applications, that means the provider setup is the first step before any component can declare multiple injectQuery calls or inspect global fetching state.
A query declaration has three important parts for these workflows. The queryKey identifies the cached resource and should include the inputs that make one result distinct from another. The queryFn returns a promise and performs the actual read. The enabled option, described in the official useQuery reference as a way to disable automatic execution and support dependent queries, is the declarative gate for chains. In Angular examples, query functions are commonly supplied to injectQuery with a callback that returns options, preserving a reactive shape that can read component state or injected services.
Sources: docs/framework/angular/reference/functions/provideTanStackQuery.md, docs/framework/angular/angular-httpclient-and-other-data-fetching-clients.md
Parallel Query Flow
Use parallel queries when two resources can be fetched independently. For example, a dashboard might need repository metadata, a user profile, and a list of notifications. Each query should have its own key and query function, because that allows each result to be cached, refetched, retried, invalidated, and observed independently. The component can then render each query’s pending, error, and success states either separately or as a combined screen state. This is usually preferable to one large query function that fetches everything, unless the backend endpoint itself is intentionally aggregate.
In Angular, parallel queries can be represented as multiple injectQuery fields on the same component. The Angular HttpClient guide demonstrates that query functions are promise-based and that HttpClient observables must be converted with lastValueFrom or firstValueFrom before being returned. That promise contract is what lets TanStack Query coordinate concurrent work regardless of whether the underlying client is fetch, graphql-request, HttpClient, or another asynchronous library. The library is protocol-agnostic, but each query function still needs to return a promise that resolves data or rejects with an error.
@Component({
selector: 'project-dashboard',
template: `
@if (repoQuery.isPending() || issuesQuery.isPending()) {
Loading dashboard...
} @else {
<repo-summary [repo]='repoQuery.data()' />
<issue-list [issues]='issuesQuery.data()' />
}
`,
})
class ProjectDashboardComponent {
private readonly http = inject(HttpClient)
readonly repoQuery = injectQuery(() => ({
queryKey: ['repo', 'tanstack', 'query'],
queryFn: () =>
lastValueFrom(
this.http.get('https://api.github.com/repos/tanstack/query'),
),
}))
readonly issuesQuery = injectQuery(() => ({
queryKey: ['issues', 'tanstack', 'query'],
queryFn: () =>
lastValueFrom(
this.http.get('https://api.github.com/repos/tanstack/query/issues'),
),
}))
}Sources: docs/framework/angular/angular-httpclient-and-other-data-fetching-clients.md
Dependent Query Flow
Use a dependent query when the second request cannot be described until the first request succeeds. A common example is loading a user by email and then loading projects by that user’s id. The first query key should describe the lookup input, such as ['user', email]. The second query key should describe the downstream resource and include the derived id, such as ['projects', userId]. The important detail is that the second query should exist declaratively even before it runs, with enabled set so it does not execute until userId is available.
Dependent queries should not hide cache identity behind a single composite query unless the downstream data is never reused independently. Keeping the second query separate means project data can be invalidated by its own key, warmed with prefetching, observed by another component, or inspected in devtools. It also makes loading states more precise. The first query can be pending while the second is not yet enabled; after the first succeeds, the second query transitions into its own fetch lifecycle. That distinction helps avoid UI that suggests all work is blocked when only a downstream resource is waiting for an id.
@Component({
selector: 'user-projects',
template: `
@if (userQuery.isPending()) {
Finding user...
} @else if (projectsQuery.isPending()) {
Loading projects...
} @else if (projectsQuery.isSuccess()) {
@for (project of projectsQuery.data(); track project.id) {
<project-card [project]='project' />
}
}
`,
})
class UserProjectsComponent {
private readonly http = inject(HttpClient)
readonly email = input.required<string>()
readonly userQuery = injectQuery(() => ({
queryKey: ['user', this.email()],
queryFn: () =>
lastValueFrom(this.http.get<User>(`/api/users/${this.email()}`)),
}))
readonly projectsQuery = injectQuery(() => {
const userId = this.userQuery.data()?.id
return {
queryKey: ['projects', userId],
enabled: !!userId,
queryFn: () =>
lastValueFrom(this.http.get<Project[]>(`/api/users/${userId}/projects`)),
}
})
}Sources: docs/framework/angular/angular-httpclient-and-other-data-fetching-clients.md, docs/framework/angular/guides/background-fetching-indicators.md
Background Fetching and UI Feedback
Parallel and dependent workflows become easier to reason about when the UI separates initial loading from background fetching. The Angular background fetching guide shows a component that renders Loading for the pending state, an error branch for failures, and a success branch that can still display Refreshing while isFetching is true. That pattern is especially useful for parallel screens because one query may already have data while another is refreshing in the background. It also prevents the user interface from collapsing back to a full loading screen whenever cached data is being updated.
For app-wide feedback, Angular Query exposes injectIsFetching, shown in the guide as a global loading indicator that renders when any query is fetching in the background. This is a good companion for parallel query pages because it avoids wiring individual query states through unrelated layout components. It is also useful with dependent chains: after the first query enables the second, the global indicator can communicate that the application is still fetching without forcing each feature component to invent its own shared request counter.
Sources: docs/framework/angular/guides/background-fetching-indicators.md
Debugging and Documentation Signals
Devtools are the most direct way to inspect whether a group of queries is truly parallel or accidentally serialized. The Angular devtools documentation says devtools help debug and inspect queries and mutations, and they can be enabled by passing withDevtools to provideTanStackQuery. In development builds they are loaded automatically by default, while production loading can be controlled with the production subpath and the loadDevtools option. When working on dependent queries, use devtools to verify keys, enabled transitions, stale status, cached data, and background refetching behavior.
The docs configuration places framework documentation under sections for React, Solid, Vue, Svelte, Lit, and other adapters, which reinforces that the concepts are shared even when the API shape changes. The community resources page points readers to maintainer-written posts, videos, and utilities such as Query Key Factory, GraphQL Code Generator, Orval, and React Query Kit. Those resources are useful once a team has several parallel or dependent workflows and wants stronger conventions for query key construction, generated API clients, or reusable typed hooks.
Sources: docs/framework/angular/devtools.md, docs/config.json, docs/community-resources.md
Relevant Source Files
- docs/framework/angular/reference/functions/provideTanStackQuery.md — Defines the Angular provider entry point, shows QueryClient setup, optional withDevtools configuration, and the InjectionToken optimization for lazy-loaded routes.
- docs/framework/angular/angular-httpclient-and-other-data-fetching-clients.md — Explains that TanStack Query query functions are promise-based, shows Angular HttpClient usage through lastValueFrom, and compares HttpClient, fetch, and specialized clients.
- docs/framework/angular/guides/background-fetching-indicators.md — Demonstrates component-level isFetching UI and the global injectIsFetching indicator used to surface background work across multiple queries.
- docs/framework/angular/devtools.md — Documents Angular devtools setup through withDevtools, development-only loading defaults, production subpath behavior, and reactive loadDevtools options.
- docs/config.json — Shows the first-party documentation structure across frameworks and confirms where framework-specific guides live in the docs navigation.
- docs/community-resources.md — Lists community learning material and utilities that support query-key conventions, generated clients, and reusable Query patterns.
Practical Checklist
Start by giving every independent resource its own stable query key and promise-returning query function. If two reads do not need each other’s output, declare them side by side and let the QueryClient coordinate concurrency. If one read needs data from another, derive the downstream key from the upstream result and gate it with enabled. In Angular, convert HttpClient observables to promises inside queryFn, prefer component templates that distinguish pending from background fetching, and add devtools early so query keys and fetch state are visible while the workflow is still being designed.
Next, review the query key and query function pages before standardizing large screens with many reads. Then read background refetching and devtools guidance so the UI communicates refresh work without hiding cached data. For Angular applications specifically, confirm provideTanStackQuery is registered once at the appropriate application boundary, then decide whether global injectIsFetching and devtools should be part of the shared shell for debugging multi-query pages.