or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.mdinfinite-queries.mdmulti-query-operations.mdmutation-management.mdoptions-helpers.mdprovider-setup.mdquery-management.mdstatus-monitoring.md

status-monitoring.mddocs/

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

```