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

query-management.mddocs/

Query Management

Core query functionality for fetching and caching data with automatic refetching, stale-while-revalidate patterns, and reactive updates using Angular signals.

Capabilities

Inject Query

Creates a reactive query that automatically fetches data and provides signals for all query states.

/**
 * Injects a query: a declarative dependency on an asynchronous source of data that is tied to a unique key.
 * @param injectQueryFn - A function that returns query options
 * @param options - Additional configuration including custom injector
 * @returns The query result with signals for reactive access
 */
function injectQuery<TQueryFnData, TError, TData, TQueryKey>(
  injectQueryFn: () => CreateQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
  options?: InjectQueryOptions
): CreateQueryResult<TData, TError>;

// Overloads for different initial data scenarios
function injectQuery<TQueryFnData, TError, TData, TQueryKey>(
  injectQueryFn: () => DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
  options?: InjectQueryOptions
): DefinedCreateQueryResult<TData, TError>;

function injectQuery<TQueryFnData, TError, TData, TQueryKey>(
  injectQueryFn: () => UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
  options?: InjectQueryOptions
): CreateQueryResult<TData, TError>;

Usage Examples:

import { injectQuery } from "@tanstack/angular-query-experimental";
import { Component, inject, signal } from "@angular/core";
import { HttpClient } from "@angular/common/http";

@Component({
  selector: 'app-user-profile',
  template: `
    <div *ngIf="userQuery.isPending()">Loading user...</div>
    <div *ngIf="userQuery.isError()">Error: {{ userQuery.error()?.message }}</div>
    <div *ngIf="userQuery.isSuccess()">
      <h2>{{ userQuery.data()?.name }}</h2>
      <p>{{ userQuery.data()?.email }}</p>
    </div>
  `
})
export class UserProfileComponent {
  #http = inject(HttpClient);
  userId = signal(1);
  
  // Basic query
  userQuery = injectQuery(() => ({
    queryKey: ['user', this.userId()],
    queryFn: () => this.#http.get<User>(`/api/users/${this.userId()}`),
    staleTime: 5 * 60 * 1000, // 5 minutes
    refetchOnWindowFocus: true
  }));
  
  // Query with enabled condition
  userDetailsQuery = injectQuery(() => ({
    queryKey: ['userDetails', this.userId()],
    queryFn: () => this.#http.get<UserDetails>(`/api/users/${this.userId()}/details`),
    enabled: !!this.userId() && this.userId() > 0
  }));
}

interface User {
  id: number;
  name: string;
  email: string;
}

interface UserDetails {
  bio: string;
  joinDate: string;
  lastLogin: string;
}

Query Options Interface

Comprehensive options for configuring query behavior.

interface CreateQueryOptions<TQueryFnData, TError, TData, TQueryKey> {
  /** Unique key for the query, used for caching and invalidation */
  queryKey: TQueryKey;
  /** Function that returns a promise resolving to the data */
  queryFn: QueryFunction<TQueryFnData, TQueryKey>;
  /** Whether the query should automatically execute */
  enabled?: boolean;
  /** Time in milliseconds after which data is considered stale */
  staleTime?: number;
  /** Time in milliseconds after which unused data is garbage collected */
  gcTime?: number;
  /** Whether to refetch when window regains focus */
  refetchOnWindowFocus?: boolean;
  /** Whether to refetch when component reconnects */
  refetchOnReconnect?: boolean;
  /** Interval in milliseconds for automatic refetching */
  refetchInterval?: number;
  /** Whether to continue refetching while window is hidden */
  refetchIntervalInBackground?: boolean;
  /** Number of retry attempts on failure */
  retry?: boolean | number | ((failureCount: number, error: TError) => boolean);
  /** Delay function for retry attempts */
  retryDelay?: number | ((retryAttempt: number, error: TError) => number);
  /** Initial data to use while loading */
  initialData?: TData | (() => TData);
  /** Placeholder data to show while loading */
  placeholderData?: TData | ((previousData: TData | undefined) => TData);
  /** Function to transform query data */
  select?: (data: TQueryFnData) => TData;
  /** Whether to throw errors instead of setting error state */
  throwOnError?: boolean | ((error: TError) => boolean);
  /** Structural sharing to optimize re-renders */
  structuralSharing?: boolean | ((oldData: TData | undefined, newData: TData) => TData);
}

Query Result Interface

Signal-based result object providing reactive access to query state.

interface CreateQueryResult<TData, TError> {
  /** Signal containing the query data */
  data: Signal<TData | undefined>;
  /** Signal containing any error that occurred */
  error: Signal<TError | null>;
  /** Signal indicating if query is currently loading (first time) */
  isLoading: Signal<boolean>;
  /** Signal indicating if query is pending (loading or fetching) */
  isPending: Signal<boolean>;
  /** Signal indicating if query completed successfully */
  isSuccess: Signal<boolean>;
  /** Signal indicating if query resulted in error */
  isError: Signal<boolean>;
  /** Signal indicating if query is currently fetching */
  isFetching: Signal<boolean>;
  /** Signal indicating if query is refetching in background */
  isRefetching: Signal<boolean>;
  /** Signal indicating if query is stale */
  isStale: Signal<boolean>;
  /** Signal containing query status */
  status: Signal<'pending' | 'error' | 'success'>;
  /** Signal containing fetch status */
  fetchStatus: Signal<'fetching' | 'paused' | 'idle'>;
  /** Signal containing timestamp of last successful fetch */
  dataUpdatedAt: Signal<number>;
  /** Signal containing timestamp of last error */
  errorUpdatedAt: Signal<number>;
  /** Signal containing current failure count */
  failureCount: Signal<number>;
  /** Signal containing failure reason */
  failureReason: Signal<TError | null>;
  
  // Type narrowing methods
  isSuccess(this: CreateQueryResult<TData, TError>): this is CreateQueryResult<TData, TError>;
  isError(this: CreateQueryResult<TData, TError>): this is CreateQueryResult<TData, TError>;
  isPending(this: CreateQueryResult<TData, TError>): this is CreateQueryResult<TData, TError>;
}

interface DefinedCreateQueryResult<TData, TError> extends CreateQueryResult<TData, TError> {
  /** Signal containing the query data (guaranteed to be defined) */
  data: Signal<TData>;
}

Options Configuration

Configuration interface for injectQuery behavior.

interface InjectQueryOptions {
  /**
   * The Injector in which to create the query.
   * If not provided, the current injection context will be used instead (via inject).
   */
  injector?: Injector;
}

Helper Types for Initial Data

Type definitions for different initial data scenarios.

type UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> = 
  CreateQueryOptions<TQueryFnData, TError, TData, TQueryKey> & {
    initialData?: undefined | InitialDataFunction<NonUndefinedGuard<TQueryFnData>> | NonUndefinedGuard<TQueryFnData>;
  };

type DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> = 
  Omit<CreateQueryOptions<TQueryFnData, TError, TData, TQueryKey>, 'queryFn'> & {
    initialData: NonUndefinedGuard<TQueryFnData> | (() => NonUndefinedGuard<TQueryFnData>);
    queryFn?: QueryFunction<TQueryFnData, TQueryKey>;
  };

Advanced Usage Patterns

Reactive Queries with Signals

@Component({})
export class ReactiveQueryComponent {
  #http = inject(HttpClient);
  
  // Signal for reactive dependency
  searchTerm = signal('');
  
  // Query automatically updates when searchTerm changes
  searchQuery = injectQuery(() => ({
    queryKey: ['search', this.searchTerm()],
    queryFn: () => this.#http.get<SearchResult[]>(`/api/search?q=${this.searchTerm()}`),
    enabled: this.searchTerm().length > 2, // Only search if term is long enough
    staleTime: 30000 // 30 seconds
  }));
}

Dependent Queries

@Component({})
export class DependentQueriesComponent {
  #http = inject(HttpClient);
  
  userQuery = injectQuery(() => ({
    queryKey: ['user'],
    queryFn: () => this.#http.get<User>('/api/user')
  }));
  
  // This query depends on userQuery data
  userPostsQuery = injectQuery(() => ({
    queryKey: ['posts', this.userQuery.data()?.id],
    queryFn: () => this.#http.get<Post[]>(`/api/users/${this.userQuery.data()!.id}/posts`),
    enabled: !!this.userQuery.data()?.id
  }));
}

Error Handling

@Component({})
export class ErrorHandlingComponent {
  #http = inject(HttpClient);
  
  dataQuery = injectQuery(() => ({
    queryKey: ['data'],
    queryFn: () => this.#http.get<Data>('/api/data'),
    retry: (failureCount, error) => {
      // Only retry on network errors, not 4xx errors
      return failureCount < 3 && error.status >= 500;
    },
    retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
    throwOnError: false // Handle errors in component instead of throwing
  }));
  
  // Access error state
  get errorMessage() {
    const error = this.dataQuery.error();
    return error ? `Failed to load data: ${error.message}` : '';
  }
}

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