0
# Status Monitoring
1
2
Functions for monitoring query and mutation states across the application for loading indicators and debugging using Angular signals.
3
4
## Capabilities
5
6
### Inject Is Fetching
7
8
Tracks the number of queries that are currently loading or fetching in the background.
9
10
```typescript { .api }
11
/**
12
* Injects a signal that tracks the number of queries that your application is loading or
13
* fetching in the background. Can be used for app-wide loading indicators.
14
* @param filters - The filters to apply to the query
15
* @param options - Additional configuration including custom injector
16
* @returns signal with number of loading or fetching queries
17
*/
18
function injectIsFetching(
19
filters?: QueryFilters,
20
options?: InjectIsFetchingOptions
21
): Signal<number>;
22
23
interface InjectIsFetchingOptions {
24
/** The Injector in which to create the isFetching signal */
25
injector?: Injector;
26
}
27
28
interface QueryFilters {
29
/** Filter by query key */
30
queryKey?: QueryKey;
31
/** Filter by exact query key match */
32
exact?: boolean;
33
/** Filter by query type */
34
type?: 'active' | 'inactive' | 'all';
35
/** Filter by stale status */
36
stale?: boolean;
37
/** Filter by fetch status */
38
fetchStatus?: 'fetching' | 'paused' | 'idle';
39
/** Custom predicate function */
40
predicate?: (query: Query) => boolean;
41
}
42
```
43
44
**Usage Examples:**
45
46
```typescript
47
import { injectIsFetching } from "@tanstack/angular-query-experimental";
48
import { Component } from "@angular/core";
49
50
@Component({
51
selector: 'app-global-loading',
52
template: `
53
<div
54
class="global-loading-indicator"
55
[class.visible]="isFetching() > 0"
56
>
57
Loading {{ isFetching() }} {{ isFetching() === 1 ? 'query' : 'queries' }}...
58
</div>
59
`
60
})
61
export class GlobalLoadingComponent {
62
// Track all fetching queries
63
isFetching = injectIsFetching();
64
65
// Track only specific queries
66
userQueriesFetching = injectIsFetching({
67
queryKey: ['users'],
68
exact: false // Include all queries that start with ['users']
69
});
70
71
// Track only active queries
72
activeFetching = injectIsFetching({
73
type: 'active'
74
});
75
}
76
77
@Component({
78
selector: 'app-section-loading',
79
template: `
80
<div class="section">
81
<h2>User Management</h2>
82
<div *ngIf="userSectionLoading() > 0" class="loading-bar">
83
Loading user data...
84
</div>
85
<!-- User content -->
86
</div>
87
`
88
})
89
export class UserSectionComponent {
90
// Track only user-related queries
91
userSectionLoading = injectIsFetching({
92
predicate: (query) => {
93
const key = query.queryKey[0];
94
return typeof key === 'string' &&
95
(key.includes('user') || key.includes('profile') || key.includes('account'));
96
}
97
});
98
}
99
```
100
101
### Inject Is Mutating
102
103
Tracks the number of mutations that are currently running.
104
105
```typescript { .api }
106
/**
107
* Injects a signal that tracks the number of mutations that your application is fetching.
108
* Can be used for app-wide loading indicators.
109
* @param filters - The filters to apply to the mutation
110
* @param options - Additional configuration including custom injector
111
* @returns signal with number of fetching mutations
112
*/
113
function injectIsMutating(
114
filters?: MutationFilters,
115
options?: InjectIsMutatingOptions
116
): Signal<number>;
117
118
interface InjectIsMutatingOptions {
119
/** The Injector in which to create the isMutating signal */
120
injector?: Injector;
121
}
122
123
interface MutationFilters {
124
/** Filter by mutation key */
125
mutationKey?: MutationKey;
126
/** Filter by exact mutation key match */
127
exact?: boolean;
128
/** Filter by mutation status */
129
status?: 'idle' | 'pending' | 'error' | 'success';
130
/** Custom predicate function */
131
predicate?: (mutation: Mutation) => boolean;
132
}
133
```
134
135
**Usage Examples:**
136
137
```typescript
138
import { injectIsMutating } from "@tanstack/angular-query-experimental";
139
import { Component } from "@angular/core";
140
141
@Component({
142
selector: 'app-save-indicator',
143
template: `
144
<div
145
class="save-indicator"
146
[class.saving]="isMutating() > 0"
147
>
148
<span *ngIf="isMutating() > 0">
149
Saving {{ isMutating() }} {{ isMutating() === 1 ? 'item' : 'items' }}...
150
</span>
151
<span *ngIf="isMutating() === 0">
152
All changes saved
153
</span>
154
</div>
155
`
156
})
157
export class SaveIndicatorComponent {
158
// Track all running mutations
159
isMutating = injectIsMutating();
160
161
// Track only user-related mutations
162
userMutations = injectIsMutating({
163
predicate: (mutation) => {
164
const key = mutation.options.mutationKey?.[0];
165
return typeof key === 'string' && key.startsWith('user');
166
}
167
});
168
169
// Track only pending mutations
170
pendingMutations = injectIsMutating({
171
status: 'pending'
172
});
173
}
174
175
@Component({
176
selector: 'app-form-controls',
177
template: `
178
<div class="form-actions">
179
<button
180
type="submit"
181
[disabled]="isSubmitting() > 0"
182
>
183
{{ isSubmitting() > 0 ? 'Submitting...' : 'Submit' }}
184
</button>
185
186
<button
187
type="button"
188
[disabled]="isSubmitting() > 0"
189
(click)="cancel()"
190
>
191
Cancel
192
</button>
193
</div>
194
`
195
})
196
export class FormControlsComponent {
197
// Track form submission mutations
198
isSubmitting = injectIsMutating({
199
mutationKey: ['submitForm']
200
});
201
}
202
```
203
204
### Inject Is Restoring
205
206
Tracks whether a restore operation is currently in progress, used for persistence scenarios.
207
208
```typescript { .api }
209
/**
210
* Injects a signal that tracks whether a restore is currently in progress.
211
* injectQuery and friends also check this internally to avoid race conditions
212
* between the restore and initializing queries.
213
* @param options - Options for injectIsRestoring including custom injector
214
* @returns signal with boolean that indicates whether a restore is in progress
215
*/
216
function injectIsRestoring(options?: InjectIsRestoringOptions): Signal<boolean>;
217
218
interface InjectIsRestoringOptions {
219
/** The Injector in which to create the isRestoring signal */
220
injector?: Injector;
221
}
222
```
223
224
**Usage Examples:**
225
226
```typescript
227
import { injectIsRestoring } from "@tanstack/angular-query-experimental";
228
import { Component } from "@angular/core";
229
230
@Component({
231
selector: 'app-root',
232
template: `
233
<div class="app">
234
<div *ngIf="isRestoring()" class="restore-overlay">
235
<div class="restore-message">
236
Restoring your data...
237
</div>
238
</div>
239
240
<router-outlet *ngIf="!isRestoring()"></router-outlet>
241
</div>
242
`
243
})
244
export class AppComponent {
245
isRestoring = injectIsRestoring();
246
}
247
248
@Component({
249
selector: 'app-data-manager',
250
template: `
251
<div class="data-status">
252
<div *ngIf="isRestoring()" class="status-item">
253
<span class="icon">π</span>
254
Restoring cached data...
255
</div>
256
257
<div *ngIf="!isRestoring() && isFetching() > 0" class="status-item">
258
<span class="icon">π‘</span>
259
Fetching {{ isFetching() }} queries...
260
</div>
261
262
<div *ngIf="!isRestoring() && isMutating() > 0" class="status-item">
263
<span class="icon">πΎ</span>
264
Saving {{ isMutating() }} changes...
265
</div>
266
</div>
267
`
268
})
269
export class DataManagerComponent {
270
isRestoring = injectIsRestoring();
271
isFetching = injectIsFetching();
272
isMutating = injectIsMutating();
273
}
274
```
275
276
## Advanced Usage Patterns
277
278
### Combined Status Indicators
279
280
```typescript
281
@Component({
282
selector: 'app-network-status',
283
template: `
284
<div class="network-status" [ngClass]="statusClass()">
285
<div class="status-text">{{ statusText() }}</div>
286
<div class="status-details">
287
<span *ngIf="isFetching() > 0">{{ isFetching() }} fetching</span>
288
<span *ngIf="isMutating() > 0">{{ isMutating() }} saving</span>
289
<span *ngIf="isRestoring()">Restoring</span>
290
</div>
291
</div>
292
`
293
})
294
export class NetworkStatusComponent {
295
isRestoring = injectIsRestoring();
296
isFetching = injectIsFetching();
297
isMutating = injectIsMutating();
298
299
statusClass = computed(() => {
300
if (this.isRestoring()) return 'status-restoring';
301
if (this.isMutating() > 0) return 'status-saving';
302
if (this.isFetching() > 0) return 'status-loading';
303
return 'status-idle';
304
});
305
306
statusText = computed(() => {
307
if (this.isRestoring()) return 'Restoring data...';
308
if (this.isMutating() > 0) return 'Saving changes...';
309
if (this.isFetching() > 0) return 'Loading data...';
310
return 'Ready';
311
});
312
}
313
```
314
315
### Filtered Status Monitoring
316
317
```typescript
318
@Component({})
319
export class FilteredStatusComponent {
320
// Monitor different types of operations separately
321
backgroundRefetch = injectIsFetching({
322
predicate: (query) => query.state.fetchStatus === 'fetching' && !query.state.isLoading
323
});
324
325
initialLoading = injectIsFetching({
326
predicate: (query) => query.state.isLoading
327
});
328
329
criticalMutations = injectIsMutating({
330
predicate: (mutation) => {
331
const key = mutation.options.mutationKey?.[0];
332
return typeof key === 'string' &&
333
['deleteUser', 'submitPayment', 'publishPost'].includes(key);
334
}
335
});
336
337
// Computed status based on different priorities
338
overallStatus = computed(() => {
339
if (this.criticalMutations() > 0) return 'critical-saving';
340
if (this.initialLoading() > 0) return 'initial-loading';
341
if (this.backgroundRefetch() > 0) return 'background-update';
342
return 'idle';
343
});
344
}
345
```
346
347
### Custom Status Service
348
349
```typescript
350
import { Injectable, computed, signal } from '@angular/core';
351
import { injectIsFetching, injectIsMutating, injectIsRestoring } from '@tanstack/angular-query-experimental';
352
353
@Injectable({ providedIn: 'root' })
354
export class AppStatusService {
355
private isRestoring = injectIsRestoring();
356
private isFetching = injectIsFetching();
357
private isMutating = injectIsMutating();
358
359
// Custom status tracking
360
private _isOnline = signal(navigator.onLine);
361
private _hasError = signal(false);
362
363
// Computed overall app status
364
readonly appStatus = computed(() => {
365
if (!this._isOnline()) return 'offline';
366
if (this._hasError()) return 'error';
367
if (this.isRestoring()) return 'restoring';
368
if (this.isMutating() > 0) return 'saving';
369
if (this.isFetching() > 0) return 'loading';
370
return 'ready';
371
});
372
373
readonly isBusy = computed(() => {
374
return this.isRestoring() || this.isFetching() > 0 || this.isMutating() > 0;
375
});
376
377
constructor() {
378
// Listen for online/offline events
379
window.addEventListener('online', () => this._isOnline.set(true));
380
window.addEventListener('offline', () => this._isOnline.set(false));
381
}
382
383
setError(hasError: boolean) {
384
this._hasError.set(hasError);
385
}
386
387
get statusMessage() {
388
switch (this.appStatus()) {
389
case 'offline': return 'You are offline';
390
case 'error': return 'Something went wrong';
391
case 'restoring': return 'Restoring your data...';
392
case 'saving': return 'Saving changes...';
393
case 'loading': return 'Loading...';
394
case 'ready': return 'Ready';
395
default: return '';
396
}
397
}
398
}
399
400
// Usage in components
401
@Component({})
402
export class SomeComponent {
403
private statusService = inject(AppStatusService);
404
405
appStatus = this.statusService.appStatus;
406
isBusy = this.statusService.isBusy;
407
statusMessage = this.statusService.statusMessage;
408
}
409
```
410
411
### Progress Tracking
412
413
```typescript
414
@Component({
415
template: `
416
<div class="progress-container">
417
<div class="progress-bar">
418
<div
419
class="progress-fill"
420
[style.width.%]="progressPercentage()"
421
></div>
422
</div>
423
<div class="progress-text">
424
{{ progressText() }}
425
</div>
426
</div>
427
`
428
})
429
export class ProgressTrackingComponent {
430
private totalOperations = signal(0);
431
private completedOperations = signal(0);
432
433
isFetching = injectIsFetching();
434
isMutating = injectIsMutating();
435
436
progressPercentage = computed(() => {
437
const total = this.totalOperations();
438
const completed = this.completedOperations();
439
return total > 0 ? (completed / total) * 100 : 0;
440
});
441
442
progressText = computed(() => {
443
const fetching = this.isFetching();
444
const mutating = this.isMutating();
445
const total = fetching + mutating;
446
447
if (total === 0) return 'All operations complete';
448
return `${total} operations in progress`;
449
});
450
451
// Methods to update progress (called by parent components)
452
updateProgress(total: number, completed: number) {
453
this.totalOperations.set(total);
454
this.completedOperations.set(completed);
455
}
456
}
457
```