CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-tanstack--angular-query-experimental

Signals for managing, caching and syncing asynchronous and remote data in Angular

Pending
Overview
Eval results
Files

status-monitoring.mddocs/

Status Monitoring

Functions for monitoring query and mutation states across the application for loading indicators and debugging using Angular signals.

Capabilities

Inject Is Fetching

Tracks the number of queries that are currently loading or fetching in the background.

/**
 * Injects a signal that tracks the number of queries that your application is loading or
 * fetching in the background. Can be used for app-wide loading indicators.
 * @param filters - The filters to apply to the query
 * @param options - Additional configuration including custom injector
 * @returns signal with number of loading or fetching queries
 */
function injectIsFetching(
  filters?: QueryFilters,
  options?: InjectIsFetchingOptions
): Signal<number>;

interface InjectIsFetchingOptions {
  /** The Injector in which to create the isFetching signal */
  injector?: Injector;
}

interface QueryFilters {
  /** Filter by query key */
  queryKey?: QueryKey;
  /** Filter by exact query key match */
  exact?: boolean;
  /** Filter by query type */
  type?: 'active' | 'inactive' | 'all';
  /** Filter by stale status */
  stale?: boolean;
  /** Filter by fetch status */
  fetchStatus?: 'fetching' | 'paused' | 'idle';
  /** Custom predicate function */
  predicate?: (query: Query) => boolean;
}

Usage Examples:

import { injectIsFetching } from "@tanstack/angular-query-experimental";
import { Component } from "@angular/core";

@Component({
  selector: 'app-global-loading',
  template: `
    <div 
      class="global-loading-indicator" 
      [class.visible]="isFetching() > 0"
    >
      Loading {{ isFetching() }} {{ isFetching() === 1 ? 'query' : 'queries' }}...
    </div>
  `
})
export class GlobalLoadingComponent {
  // Track all fetching queries
  isFetching = injectIsFetching();
  
  // Track only specific queries
  userQueriesFetching = injectIsFetching({
    queryKey: ['users'],
    exact: false // Include all queries that start with ['users']
  });
  
  // Track only active queries
  activeFetching = injectIsFetching({
    type: 'active'
  });
}

@Component({
  selector: 'app-section-loading',
  template: `
    <div class="section">
      <h2>User Management</h2>
      <div *ngIf="userSectionLoading() > 0" class="loading-bar">
        Loading user data...
      </div>
      <!-- User content -->
    </div>
  `
})
export class UserSectionComponent {
  // Track only user-related queries
  userSectionLoading = injectIsFetching({
    predicate: (query) => {
      const key = query.queryKey[0];
      return typeof key === 'string' && 
             (key.includes('user') || key.includes('profile') || key.includes('account'));
    }
  });
}

Inject Is Mutating

Tracks the number of mutations that are currently running.

/**
 * Injects a signal that tracks the number of mutations that your application is fetching.
 * Can be used for app-wide loading indicators.
 * @param filters - The filters to apply to the mutation
 * @param options - Additional configuration including custom injector
 * @returns signal with number of fetching mutations
 */
function injectIsMutating(
  filters?: MutationFilters,
  options?: InjectIsMutatingOptions
): Signal<number>;

interface InjectIsMutatingOptions {
  /** The Injector in which to create the isMutating signal */
  injector?: Injector;
}

interface MutationFilters {
  /** Filter by mutation key */
  mutationKey?: MutationKey;
  /** Filter by exact mutation key match */
  exact?: boolean;
  /** Filter by mutation status */
  status?: 'idle' | 'pending' | 'error' | 'success';
  /** Custom predicate function */
  predicate?: (mutation: Mutation) => boolean;
}

Usage Examples:

import { injectIsMutating } from "@tanstack/angular-query-experimental";
import { Component } from "@angular/core";

@Component({
  selector: 'app-save-indicator',
  template: `
    <div 
      class="save-indicator" 
      [class.saving]="isMutating() > 0"
    >
      <span *ngIf="isMutating() > 0">
        Saving {{ isMutating() }} {{ isMutating() === 1 ? 'item' : 'items' }}...
      </span>
      <span *ngIf="isMutating() === 0">
        All changes saved
      </span>
    </div>
  `
})
export class SaveIndicatorComponent {
  // Track all running mutations
  isMutating = injectIsMutating();
  
  // Track only user-related mutations
  userMutations = injectIsMutating({
    predicate: (mutation) => {
      const key = mutation.options.mutationKey?.[0];
      return typeof key === 'string' && key.startsWith('user');
    }
  });
  
  // Track only pending mutations
  pendingMutations = injectIsMutating({
    status: 'pending'
  });
}

@Component({
  selector: 'app-form-controls',
  template: `
    <div class="form-actions">
      <button 
        type="submit" 
        [disabled]="isSubmitting() > 0"
      >
        {{ isSubmitting() > 0 ? 'Submitting...' : 'Submit' }}
      </button>
      
      <button 
        type="button" 
        [disabled]="isSubmitting() > 0"
        (click)="cancel()"
      >
        Cancel
      </button>
    </div>
  `
})
export class FormControlsComponent {
  // Track form submission mutations
  isSubmitting = injectIsMutating({
    mutationKey: ['submitForm']
  });
}

Inject Is Restoring

Tracks whether a restore operation is currently in progress, used for persistence scenarios.

/**
 * Injects a signal that tracks whether a restore is currently in progress. 
 * injectQuery and friends also check this internally to avoid race conditions 
 * between the restore and initializing queries.
 * @param options - Options for injectIsRestoring including custom injector
 * @returns signal with boolean that indicates whether a restore is in progress
 */
function injectIsRestoring(options?: InjectIsRestoringOptions): Signal<boolean>;

interface InjectIsRestoringOptions {
  /** The Injector in which to create the isRestoring signal */
  injector?: Injector;
}

Usage Examples:

import { injectIsRestoring } from "@tanstack/angular-query-experimental";
import { Component } from "@angular/core";

@Component({
  selector: 'app-root',
  template: `
    <div class="app">
      <div *ngIf="isRestoring()" class="restore-overlay">
        <div class="restore-message">
          Restoring your data...
        </div>
      </div>
      
      <router-outlet *ngIf="!isRestoring()"></router-outlet>
    </div>
  `
})
export class AppComponent {
  isRestoring = injectIsRestoring();
}

@Component({
  selector: 'app-data-manager',
  template: `
    <div class="data-status">
      <div *ngIf="isRestoring()" class="status-item">
        <span class="icon">🔄</span>
        Restoring cached data...
      </div>
      
      <div *ngIf="!isRestoring() && isFetching() > 0" class="status-item">
        <span class="icon">📡</span>
        Fetching {{ isFetching() }} queries...
      </div>
      
      <div *ngIf="!isRestoring() && isMutating() > 0" class="status-item">
        <span class="icon">💾</span>
        Saving {{ isMutating() }} changes...
      </div>
    </div>
  `
})
export class DataManagerComponent {
  isRestoring = injectIsRestoring();
  isFetching = injectIsFetching();
  isMutating = injectIsMutating();
}

Advanced Usage Patterns

Combined Status Indicators

@Component({
  selector: 'app-network-status',
  template: `
    <div class="network-status" [ngClass]="statusClass()">
      <div class="status-text">{{ statusText() }}</div>
      <div class="status-details">
        <span *ngIf="isFetching() > 0">{{ isFetching() }} fetching</span>
        <span *ngIf="isMutating() > 0">{{ isMutating() }} saving</span>
        <span *ngIf="isRestoring()">Restoring</span>
      </div>
    </div>
  `
})
export class NetworkStatusComponent {
  isRestoring = injectIsRestoring();
  isFetching = injectIsFetching();
  isMutating = injectIsMutating();
  
  statusClass = computed(() => {
    if (this.isRestoring()) return 'status-restoring';
    if (this.isMutating() > 0) return 'status-saving';
    if (this.isFetching() > 0) return 'status-loading';
    return 'status-idle';
  });
  
  statusText = computed(() => {
    if (this.isRestoring()) return 'Restoring data...';
    if (this.isMutating() > 0) return 'Saving changes...';
    if (this.isFetching() > 0) return 'Loading data...';
    return 'Ready';
  });
}

Filtered Status Monitoring

@Component({})
export class FilteredStatusComponent {
  // Monitor different types of operations separately
  backgroundRefetch = injectIsFetching({
    predicate: (query) => query.state.fetchStatus === 'fetching' && !query.state.isLoading
  });
  
  initialLoading = injectIsFetching({
    predicate: (query) => query.state.isLoading
  });
  
  criticalMutations = injectIsMutating({
    predicate: (mutation) => {
      const key = mutation.options.mutationKey?.[0];
      return typeof key === 'string' && 
             ['deleteUser', 'submitPayment', 'publishPost'].includes(key);
    }
  });
  
  // Computed status based on different priorities
  overallStatus = computed(() => {
    if (this.criticalMutations() > 0) return 'critical-saving';
    if (this.initialLoading() > 0) return 'initial-loading';
    if (this.backgroundRefetch() > 0) return 'background-update';
    return 'idle';
  });
}

Custom Status Service

import { Injectable, computed, signal } from '@angular/core';
import { injectIsFetching, injectIsMutating, injectIsRestoring } from '@tanstack/angular-query-experimental';

@Injectable({ providedIn: 'root' })
export class AppStatusService {
  private isRestoring = injectIsRestoring();
  private isFetching = injectIsFetching();
  private isMutating = injectIsMutating();
  
  // Custom status tracking
  private _isOnline = signal(navigator.onLine);
  private _hasError = signal(false);
  
  // Computed overall app status
  readonly appStatus = computed(() => {
    if (!this._isOnline()) return 'offline';
    if (this._hasError()) return 'error';
    if (this.isRestoring()) return 'restoring';
    if (this.isMutating() > 0) return 'saving';
    if (this.isFetching() > 0) return 'loading';
    return 'ready';
  });
  
  readonly isBusy = computed(() => {
    return this.isRestoring() || this.isFetching() > 0 || this.isMutating() > 0;
  });
  
  constructor() {
    // Listen for online/offline events
    window.addEventListener('online', () => this._isOnline.set(true));
    window.addEventListener('offline', () => this._isOnline.set(false));
  }
  
  setError(hasError: boolean) {
    this._hasError.set(hasError);
  }
  
  get statusMessage() {
    switch (this.appStatus()) {
      case 'offline': return 'You are offline';
      case 'error': return 'Something went wrong';
      case 'restoring': return 'Restoring your data...';
      case 'saving': return 'Saving changes...';
      case 'loading': return 'Loading...';
      case 'ready': return 'Ready';
      default: return '';
    }
  }
}

// Usage in components
@Component({})
export class SomeComponent {
  private statusService = inject(AppStatusService);
  
  appStatus = this.statusService.appStatus;
  isBusy = this.statusService.isBusy;
  statusMessage = this.statusService.statusMessage;
}

Progress Tracking

@Component({
  template: `
    <div class="progress-container">
      <div class="progress-bar">
        <div 
          class="progress-fill" 
          [style.width.%]="progressPercentage()"
        ></div>
      </div>
      <div class="progress-text">
        {{ progressText() }}
      </div>
    </div>
  `
})
export class ProgressTrackingComponent {
  private totalOperations = signal(0);
  private completedOperations = signal(0);
  
  isFetching = injectIsFetching();
  isMutating = injectIsMutating();
  
  progressPercentage = computed(() => {
    const total = this.totalOperations();
    const completed = this.completedOperations();
    return total > 0 ? (completed / total) * 100 : 0;
  });
  
  progressText = computed(() => {
    const fetching = this.isFetching();
    const mutating = this.isMutating();
    const total = fetching + mutating;
    
    if (total === 0) return 'All operations complete';
    return `${total} operations in progress`;
  });
  
  // Methods to update progress (called by parent components)
  updateProgress(total: number, completed: number) {
    this.totalOperations.set(total);
    this.completedOperations.set(completed);
  }
}

Install with Tessl CLI

npx tessl i tessl/npm-tanstack--angular-query-experimental

docs

index.md

infinite-queries.md

multi-query-operations.md

mutation-management.md

options-helpers.md

provider-setup.md

query-management.md

status-monitoring.md

tile.json