0
# Query Management
1
2
Core query functionality for fetching and caching data with automatic refetching, stale-while-revalidate patterns, and reactive updates using Angular signals.
3
4
## Capabilities
5
6
### Inject Query
7
8
Creates a reactive query that automatically fetches data and provides signals for all query states.
9
10
```typescript { .api }
11
/**
12
* Injects a query: a declarative dependency on an asynchronous source of data that is tied to a unique key.
13
* @param injectQueryFn - A function that returns query options
14
* @param options - Additional configuration including custom injector
15
* @returns The query result with signals for reactive access
16
*/
17
function injectQuery<TQueryFnData, TError, TData, TQueryKey>(
18
injectQueryFn: () => CreateQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
19
options?: InjectQueryOptions
20
): CreateQueryResult<TData, TError>;
21
22
// Overloads for different initial data scenarios
23
function injectQuery<TQueryFnData, TError, TData, TQueryKey>(
24
injectQueryFn: () => DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
25
options?: InjectQueryOptions
26
): DefinedCreateQueryResult<TData, TError>;
27
28
function injectQuery<TQueryFnData, TError, TData, TQueryKey>(
29
injectQueryFn: () => UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
30
options?: InjectQueryOptions
31
): CreateQueryResult<TData, TError>;
32
```
33
34
**Usage Examples:**
35
36
```typescript
37
import { injectQuery } from "@tanstack/angular-query-experimental";
38
import { Component, inject, signal } from "@angular/core";
39
import { HttpClient } from "@angular/common/http";
40
41
@Component({
42
selector: 'app-user-profile',
43
template: `
44
<div *ngIf="userQuery.isPending()">Loading user...</div>
45
<div *ngIf="userQuery.isError()">Error: {{ userQuery.error()?.message }}</div>
46
<div *ngIf="userQuery.isSuccess()">
47
<h2>{{ userQuery.data()?.name }}</h2>
48
<p>{{ userQuery.data()?.email }}</p>
49
</div>
50
`
51
})
52
export class UserProfileComponent {
53
#http = inject(HttpClient);
54
userId = signal(1);
55
56
// Basic query
57
userQuery = injectQuery(() => ({
58
queryKey: ['user', this.userId()],
59
queryFn: () => this.#http.get<User>(`/api/users/${this.userId()}`),
60
staleTime: 5 * 60 * 1000, // 5 minutes
61
refetchOnWindowFocus: true
62
}));
63
64
// Query with enabled condition
65
userDetailsQuery = injectQuery(() => ({
66
queryKey: ['userDetails', this.userId()],
67
queryFn: () => this.#http.get<UserDetails>(`/api/users/${this.userId()}/details`),
68
enabled: !!this.userId() && this.userId() > 0
69
}));
70
}
71
72
interface User {
73
id: number;
74
name: string;
75
email: string;
76
}
77
78
interface UserDetails {
79
bio: string;
80
joinDate: string;
81
lastLogin: string;
82
}
83
```
84
85
### Query Options Interface
86
87
Comprehensive options for configuring query behavior.
88
89
```typescript { .api }
90
interface CreateQueryOptions<TQueryFnData, TError, TData, TQueryKey> {
91
/** Unique key for the query, used for caching and invalidation */
92
queryKey: TQueryKey;
93
/** Function that returns a promise resolving to the data */
94
queryFn: QueryFunction<TQueryFnData, TQueryKey>;
95
/** Whether the query should automatically execute */
96
enabled?: boolean;
97
/** Time in milliseconds after which data is considered stale */
98
staleTime?: number;
99
/** Time in milliseconds after which unused data is garbage collected */
100
gcTime?: number;
101
/** Whether to refetch when window regains focus */
102
refetchOnWindowFocus?: boolean;
103
/** Whether to refetch when component reconnects */
104
refetchOnReconnect?: boolean;
105
/** Interval in milliseconds for automatic refetching */
106
refetchInterval?: number;
107
/** Whether to continue refetching while window is hidden */
108
refetchIntervalInBackground?: boolean;
109
/** Number of retry attempts on failure */
110
retry?: boolean | number | ((failureCount: number, error: TError) => boolean);
111
/** Delay function for retry attempts */
112
retryDelay?: number | ((retryAttempt: number, error: TError) => number);
113
/** Initial data to use while loading */
114
initialData?: TData | (() => TData);
115
/** Placeholder data to show while loading */
116
placeholderData?: TData | ((previousData: TData | undefined) => TData);
117
/** Function to transform query data */
118
select?: (data: TQueryFnData) => TData;
119
/** Whether to throw errors instead of setting error state */
120
throwOnError?: boolean | ((error: TError) => boolean);
121
/** Structural sharing to optimize re-renders */
122
structuralSharing?: boolean | ((oldData: TData | undefined, newData: TData) => TData);
123
}
124
```
125
126
### Query Result Interface
127
128
Signal-based result object providing reactive access to query state.
129
130
```typescript { .api }
131
interface CreateQueryResult<TData, TError> {
132
/** Signal containing the query data */
133
data: Signal<TData | undefined>;
134
/** Signal containing any error that occurred */
135
error: Signal<TError | null>;
136
/** Signal indicating if query is currently loading (first time) */
137
isLoading: Signal<boolean>;
138
/** Signal indicating if query is pending (loading or fetching) */
139
isPending: Signal<boolean>;
140
/** Signal indicating if query completed successfully */
141
isSuccess: Signal<boolean>;
142
/** Signal indicating if query resulted in error */
143
isError: Signal<boolean>;
144
/** Signal indicating if query is currently fetching */
145
isFetching: Signal<boolean>;
146
/** Signal indicating if query is refetching in background */
147
isRefetching: Signal<boolean>;
148
/** Signal indicating if query is stale */
149
isStale: Signal<boolean>;
150
/** Signal containing query status */
151
status: Signal<'pending' | 'error' | 'success'>;
152
/** Signal containing fetch status */
153
fetchStatus: Signal<'fetching' | 'paused' | 'idle'>;
154
/** Signal containing timestamp of last successful fetch */
155
dataUpdatedAt: Signal<number>;
156
/** Signal containing timestamp of last error */
157
errorUpdatedAt: Signal<number>;
158
/** Signal containing current failure count */
159
failureCount: Signal<number>;
160
/** Signal containing failure reason */
161
failureReason: Signal<TError | null>;
162
163
// Type narrowing methods
164
isSuccess(this: CreateQueryResult<TData, TError>): this is CreateQueryResult<TData, TError>;
165
isError(this: CreateQueryResult<TData, TError>): this is CreateQueryResult<TData, TError>;
166
isPending(this: CreateQueryResult<TData, TError>): this is CreateQueryResult<TData, TError>;
167
}
168
169
interface DefinedCreateQueryResult<TData, TError> extends CreateQueryResult<TData, TError> {
170
/** Signal containing the query data (guaranteed to be defined) */
171
data: Signal<TData>;
172
}
173
```
174
175
### Options Configuration
176
177
Configuration interface for injectQuery behavior.
178
179
```typescript { .api }
180
interface InjectQueryOptions {
181
/**
182
* The Injector in which to create the query.
183
* If not provided, the current injection context will be used instead (via inject).
184
*/
185
injector?: Injector;
186
}
187
```
188
189
### Helper Types for Initial Data
190
191
Type definitions for different initial data scenarios.
192
193
```typescript { .api }
194
type UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> =
195
CreateQueryOptions<TQueryFnData, TError, TData, TQueryKey> & {
196
initialData?: undefined | InitialDataFunction<NonUndefinedGuard<TQueryFnData>> | NonUndefinedGuard<TQueryFnData>;
197
};
198
199
type DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> =
200
Omit<CreateQueryOptions<TQueryFnData, TError, TData, TQueryKey>, 'queryFn'> & {
201
initialData: NonUndefinedGuard<TQueryFnData> | (() => NonUndefinedGuard<TQueryFnData>);
202
queryFn?: QueryFunction<TQueryFnData, TQueryKey>;
203
};
204
```
205
206
## Advanced Usage Patterns
207
208
### Reactive Queries with Signals
209
210
```typescript
211
@Component({})
212
export class ReactiveQueryComponent {
213
#http = inject(HttpClient);
214
215
// Signal for reactive dependency
216
searchTerm = signal('');
217
218
// Query automatically updates when searchTerm changes
219
searchQuery = injectQuery(() => ({
220
queryKey: ['search', this.searchTerm()],
221
queryFn: () => this.#http.get<SearchResult[]>(`/api/search?q=${this.searchTerm()}`),
222
enabled: this.searchTerm().length > 2, // Only search if term is long enough
223
staleTime: 30000 // 30 seconds
224
}));
225
}
226
```
227
228
### Dependent Queries
229
230
```typescript
231
@Component({})
232
export class DependentQueriesComponent {
233
#http = inject(HttpClient);
234
235
userQuery = injectQuery(() => ({
236
queryKey: ['user'],
237
queryFn: () => this.#http.get<User>('/api/user')
238
}));
239
240
// This query depends on userQuery data
241
userPostsQuery = injectQuery(() => ({
242
queryKey: ['posts', this.userQuery.data()?.id],
243
queryFn: () => this.#http.get<Post[]>(`/api/users/${this.userQuery.data()!.id}/posts`),
244
enabled: !!this.userQuery.data()?.id
245
}));
246
}
247
```
248
249
### Error Handling
250
251
```typescript
252
@Component({})
253
export class ErrorHandlingComponent {
254
#http = inject(HttpClient);
255
256
dataQuery = injectQuery(() => ({
257
queryKey: ['data'],
258
queryFn: () => this.#http.get<Data>('/api/data'),
259
retry: (failureCount, error) => {
260
// Only retry on network errors, not 4xx errors
261
return failureCount < 3 && error.status >= 500;
262
},
263
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
264
throwOnError: false // Handle errors in component instead of throwing
265
}));
266
267
// Access error state
268
get errorMessage() {
269
const error = this.dataQuery.error();
270
return error ? `Failed to load data: ${error.message}` : '';
271
}
272
}
273
```