Skip to main content

Overview

Queries are the primary way to fetch and cache data in ngrx-rtk-query. They automatically manage loading states, errors, caching, and re-fetching, all through Angular Signals for fine-grained reactivity.

Defining a Query Endpoint

Define queries using build.query<ResultType, ArgType>() in your API definition:
import { createApi, fetchBaseQuery } from 'ngrx-rtk-query';

export interface Post {
  id: number;
  name: string;
  fetched_at: string;
}

export const postsApi = createApi({
  baseQuery: fetchBaseQuery({ baseUrl: 'http://api.localhost.com' }),
  tagTypes: ['Posts'],
  endpoints: (build) => ({
    getPosts: build.query<Post[], void>({
      query: () => ({ url: '/posts' }),
      providesTags: (result) =>
        result
          ? [...result.map(({ id }) => ({ type: 'Posts', id })), { type: 'Posts', id: 'LIST' }]
          : [{ type: 'Posts', id: 'LIST' }],
    }),
    getPost: build.query<Post, number>({
      query: (id) => `/posts/${id}`,
      providesTags: (result, error, id) => [{ type: 'Posts', id }],
    }),
  }),
});

export const { useGetPostsQuery, useGetPostQuery } = postsApi;

Basic Usage

The useXxxQuery() hook returns a signal object with all query state:
import { Component } from '@angular/core';
import { useGetPostsQuery } from './api';

@Component({
  selector: 'app-posts-list',
  standalone: true,
  template: `
    @if (postsQuery.isLoading()) {
      <p>Loading...</p>
    }
    @if (postsQuery.isError()) {
      <p>Error: {{ postsQuery.error() }}</p>
    }
    @if (postsQuery.data(); as posts) {
      @for (post of posts; track post.id) {
        <li>{{ post.name }}</li>
      }
    }
  `,
})
export class PostsListComponent {
  postsQuery = useGetPostsQuery();
}

Query State Properties

The query signal provides the following properties:
PropertyTypeDescription
data()T | undefinedThe latest successfully fetched data
currentData()T | undefinedThe data for the current query args only
isLoading()booleantrue during initial fetch with no cached data
isFetching()booleantrue whenever a request is in flight
isSuccess()booleantrue when data is available
isError()booleantrue if the query resulted in an error
error()SerializedError | FetchBaseQueryErrorError object if query failed
refetch()() => PromiseFunction to manually refetch the query

Fine-Grained Reactivity

ngrx-rtk-query provides two ways to access query state:
Use fine-grained access (query.isLoading()) for better performance. Your component will only re-render when that specific property changes.

Queries with Parameters

Queries can accept arguments in multiple forms:

Static Parameters

export class PostDetailsComponent {
  postQuery = useGetPostQuery(1); // Always fetches post with id=1
}

Signal Parameters

Pass a signal to reactively refetch when the parameter changes:
export class PostDetailsComponent {
  readonly id = input.required<number>({ transform: numberAttribute });
  
  // Automatically refetches when id() changes
  readonly postQuery = useGetPostQuery(this.id);
}

Function Parameters

Use a function for computed parameters:
export class PostDetailsComponent {
  userId = signal(1);
  filter = signal('active');
  
  // Refetches when userId() OR filter() changes
  postsQuery = useGetPostsQuery(() => ({
    userId: this.userId(),
    filter: this.filter()
  }));
}

Skip Token

Use skipToken to conditionally skip a query:
import { skipToken } from 'ngrx-rtk-query';

export class CharacterCardComponent {
  character = input<Character | undefined>(undefined);
  
  // Only fetches when character() is defined
  locationQuery = useGetLocationQuery(
    () => this.character()?.currentLocation ?? skipToken
  );
}
When a query receives skipToken, it will not make a request and the query state will show isUninitialized: true.

Query Options

Customize query behavior with options:
export class PostDetailsComponent {
  readonly id = input.required<number>();
  
  readonly postQuery = useGetPostQuery(this.id, {
    pollingInterval: 5000,           // Refetch every 5 seconds
    refetchOnMountOrArgChange: true, // Refetch on mount or when id changes
    refetchOnFocus: true,            // Refetch when window gains focus
    refetchOnReconnect: true,        // Refetch when connection restored
    skip: false,                     // Skip the query (alternative to skipToken)
  });
}

Signal or Function Options

Options can also be signals or functions:
export class PostDetailsComponent {
  readonly id = input.required<number>();
  enablePolling = signal(false);
  
  readonly postQuery = useGetPostQuery(
    this.id,
    () => ({
      pollingInterval: this.enablePolling() ? 5000 : 0,
      skip: this.id() <= 0,
    })
  );
}

Selecting Partial State

Use selectFromResult to transform or select specific fields:
export class PostNameComponent {
  readonly postQuery = useGetPostQuery(1, {
    selectFromResult: ({ data, isLoading }) => ({
      name: data?.name,
      isLoading,
    }),
  });
  
  // postQuery.name() and postQuery.isLoading() are now available
}
selectFromResult creates a derived selector. Only the returned fields will trigger re-renders.

Manual Refetching

Trigger a refetch manually using the refetch() method:
@Component({
  template: `
    <button 
      [disabled]="postQuery.isFetching()" 
      (click)="postQuery.refetch()">
      {{ postQuery.isFetching() ? 'Refreshing...' : 'Refresh' }}
    </button>
  `
})
export class PostDetailsComponent {
  readonly id = input.required<number>();
  readonly postQuery = useGetPostQuery(this.id);
}

TypeScript Type Signatures

Query Hook Type

type UseQuery<ResultType, ArgType> = (
  arg: ArgType | Signal<ArgType> | (() => ArgType | typeof skipToken),
  options?: UseQueryOptions<ResultType> | Signal<UseQueryOptions<ResultType>> | (() => UseQueryOptions<ResultType>)
) => UseQueryStateDefaultResult<ResultType> & {
  refetch: () => Promise<QueryActionCreatorResult<ResultType>>;
};

Query State Type

interface UseQueryStateDefaultResult<T> {
  // State
  data: Signal<T | undefined>;
  currentData: Signal<T | undefined>;
  error: Signal<SerializedError | FetchBaseQueryError | undefined>;
  requestId: Signal<string | undefined>;
  endpointName: Signal<string | undefined>;
  startedTimeStamp: Signal<number | undefined>;
  fulfilledTimeStamp: Signal<number | undefined>;
  
  // Status booleans
  isUninitialized: Signal<boolean>;
  isLoading: Signal<boolean>;
  isFetching: Signal<boolean>;
  isSuccess: Signal<boolean>;
  isError: Signal<boolean>;
  
  // Query metadata
  originalArgs: Signal<unknown>;
  status: Signal<QueryStatus>;
}

Best Practices

// ✅ Good - Automatically refetches when input changes
export class PostDetailsComponent {
  readonly id = input.required<number>();
  readonly postQuery = useGetPostQuery(this.id);
}

Build docs developers (and LLMs) love