npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ngrx-rtk-query

v21.3.1

Published

Angular RTK Query

Readme

MIT All Contributors

ngrx-rtk-query

ngrx-rtk-query brings RTK Query to Angular applications with signal-based generated hooks. It keeps RTK Query endpoint definitions, caching, invalidation, lazy queries, mutations, and infinite queries, then exposes Angular-friendly APIs for components and NgRx Signal Store.

Use it when you want RTK Query's data-fetching model in Angular without writing React hooks or RxJS wrappers.

Contents

Quick Start

  1. Install ngrx-rtk-query and @reduxjs/toolkit.
  2. Install @ngrx/store only for the NgRx Store runtime, or @ngrx/signals only for the Signal Store runtime. Noop Store has no extra NgRx peer.
  3. Define one RTK Query API with createApi(...).
  4. Mount that API once with provideStoreApi(api), provideNoopStoreApi(api), or withApi(api).
  5. Use the generated Angular hooks from the API: useGetPostsQuery, useLazyGetPostsQuery, useAddPostMutation, or useGetPostsInfiniteQuery.

For most Angular apps, the shortest setup is:

import { createApi, fetchBaseQuery } from 'ngrx-rtk-query';

type Post = { id: number; name: string };

export const postsApi = createApi({
  reducerPath: 'postsApi',
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  endpoints: (build) => ({
    getPosts: build.query<Post[], void>({
      query: () => '/posts',
    }),
  }),
});

export const { useGetPostsQuery } = postsApi;

Then mount it with the runtime that matches the app:

import { provideNoopStoreApi } from 'ngrx-rtk-query/noop-store';

providers: [provideNoopStoreApi(postsApi)];

Install

Install the package and RTK Query:

npm install ngrx-rtk-query @reduxjs/toolkit

Install the runtime peer you use:

# NgRx Store runtime
npm install @ngrx/store

# NgRx Signal Store runtime
npm install @ngrx/signals

@ngrx/store and @ngrx/signals are optional peer dependencies. Install only the runtime you use.

Version Compatibility

Library majors track Angular majors.

| Angular | NgRx runtime | ngrx-rtk-query | @reduxjs/toolkit | Support | | :-----: | :----------: | :------------: | :--------------: | :-----------: | | 21.x | 21.1+ | 21.1+ | ~2.11.2 | Active | | 20.x | 20.x | 20.x | ~2.9.0 | Bugs | | 18.x | 18.2+ | 18.2+ | ~2.6.0 | Bugs | | 18.x | 18.1+ | 18.1+ | ~2.5.0 | Bugs | | 18.x | 18.0+ | 18.0+ | ~2.2.5 | Critical bugs | | 17.x | 17.1+ | 17.1+ | ~2.2.1 | Critical bugs | | 16.x | n/a | 4.2+ | ~1.9.3 | Critical bugs | | 15.x | n/a | 4.1.x | 1.9.3 | None |

Only the latest Angular major in this table is actively supported. Angular libraries are compiled against Angular's major-version compatibility contract.

Core Concepts

  • API instance: the object returned by createApi(...). It owns endpoint definitions, cache identity, generated hooks, selectors, utilities, and dispatch.
  • Runtime host: the Angular integration that mounts an API instance. Use exactly one host per API instance.
  • Generated hook: an Angular-friendly function generated from an endpoint name, such as useGetPostsQuery or useAddPostMutation.
  • Fine-grained signals: hook result fields are callable signals. Prefer query.data() or query.isLoading() when reading one field.
  • Endpoint injection: use api.injectEndpoints(...) for lazy routes or feature-owned endpoints that share the same base API and cache.

Most RTK Query endpoint features still come from @reduxjs/toolkit/query: query, queryFn, tags, cache keys, transformResponse, onQueryStarted, onCacheEntryAdded, polling, refetch options, and invalidation semantics. This package adapts the hook and runtime layer to Angular.

Runtime Choices

Choose one runtime host per API instance.

| Runtime | Use when | Mount with | | ----------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------- | | NgRx Store | The application already uses NgRx Store or wants Redux DevTools integration | provideStoreApi(api) from ngrx-rtk-query/store | | Noop Store | The application does not use NgRx Store | provideNoopStoreApi(api) from ngrx-rtk-query/noop-store | | NgRx Signal Store | You want an API mounted inside a Signal Store feature | withApi(api) from ngrx-rtk-query/signal-store |

Signal Store readers can be composed separately with withApiState(api) as long as the same API instance is mounted somewhere in the app.

Import Paths

Core APIs:

import { createApi, fetchBaseQuery, skipToken } from 'ngrx-rtk-query';

Store-agnostic core entrypoint:

import { createApi, fetchBaseQuery, skipToken } from 'ngrx-rtk-query/core';

NgRx Store runtime:

import { provideStoreApi } from 'ngrx-rtk-query/store';

Noop Store runtime:

import { provideNoopStoreApi } from 'ngrx-rtk-query/noop-store';

NgRx Signal Store runtime:

import { withApi, withApiState } from 'ngrx-rtk-query/signal-store';

provideStoreApi is still available from ngrx-rtk-query during a deprecation window. Prefer ngrx-rtk-query/store for new code. Applications that do not install @ngrx/store can import core APIs from ngrx-rtk-query/core to avoid resolving the deprecated root store-provider export.

Define an API

Define endpoints with RTK Query's createApi model:

import { createApi, fetchBaseQuery } from 'ngrx-rtk-query';

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

export const postsApi = createApi({
  reducerPath: 'postsApi',
  baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com/api' }),
  tagTypes: ['Posts'],
  endpoints: (build) => ({
    getPosts: build.query<Post[], void>({
      query: () => '/posts',
      providesTags: (result) =>
        result
          ? [...result.map(({ id }) => ({ type: 'Posts' as const, 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 }],
    }),
    addPost: build.mutation<Post, Partial<Post>>({
      query: (body) => ({ url: '/posts', method: 'POST', body }),
      invalidatesTags: [{ type: 'Posts', id: 'LIST' }],
    }),
  }),
});

export const { useGetPostsQuery, useGetPostQuery, useAddPostMutation } = postsApi;

Cache Tags and Invalidation

Use RTK Query tags the same way you would in Redux Toolkit. Queries provide tags; mutations invalidate tags; invalidated active queries refetch through the mounted runtime host.

export const postsApi = createApi({
  reducerPath: 'postsApi',
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  tagTypes: ['Posts'],
  endpoints: (build) => ({
    getPosts: build.query<Post[], void>({
      query: () => '/posts',
      providesTags: (result) =>
        result
          ? [...result.map(({ id }) => ({ type: 'Posts' as const, id })), { type: 'Posts', id: 'LIST' }]
          : [{ type: 'Posts', id: 'LIST' }],
    }),
    updatePost: build.mutation<Post, Partial<Post> & Pick<Post, 'id'>>({
      query: ({ id, ...patch }) => ({ url: `/posts/${id}`, method: 'PATCH', body: patch }),
      invalidatesTags: (_result, _error, { id }) => [
        { type: 'Posts', id },
        { type: 'Posts', id: 'LIST' },
      ],
    }),
  }),
});

Use one API instance when endpoints share a cache and tag model. Create another API only for a different base URL, cache identity, or runtime host requirement.

Mount the API

NgRx Store

Use this when the app already uses NgRx Store:

import { bootstrapApplication } from '@angular/platform-browser';
import { provideStore } from '@ngrx/store';

import { provideStoreApi } from 'ngrx-rtk-query/store';

import { AppComponent } from './app/app.component';
import { postsApi } from './app/posts/api';

bootstrapApplication(AppComponent, {
  providers: [provideStore(), provideStoreApi(postsApi)],
});

Pass { setupListeners: false } when you do not want RTK Query focus and reconnect listeners.

provideStoreApi(postsApi, { setupListeners: false });

Noop Store

Use this when the app does not use NgRx Store:

import { bootstrapApplication } from '@angular/platform-browser';

import { provideNoopStoreApi } from 'ngrx-rtk-query/noop-store';

import { AppComponent } from './app/app.component';
import { postsApi } from './app/posts/api';

bootstrapApplication(AppComponent, {
  providers: [provideNoopStoreApi(postsApi)],
});

Signal Store Host

Use withApi(api) to mount an API inside a Signal Store:

import { signalStore } from '@ngrx/signals';

import { withApi } from 'ngrx-rtk-query/signal-store';

import { postsApi } from './posts/api';

export const PostsApiStore = signalStore({ providedIn: 'root' }, withApi(postsApi));

Each API instance must be mounted once. Do not mount the same API instance in multiple runtime hosts.

Use Queries

Generated query hooks return a signal-like object with fine-grained signal properties.

import { ChangeDetectionStrategy, Component } from '@angular/core';

import { useGetPostsQuery } from './api';

@Component({
  selector: 'app-posts-list',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    @if (postsQuery.isLoading()) {
      <p>Loading...</p>
    }

    @if (postsQuery.data(); as posts) {
      @for (post of posts; track post.id) {
        <a [routerLink]="['/posts', post.id]">{{ post.name }}</a>
      }
    }
  `,
})
export class PostsListComponent {
  postsQuery = useGetPostsQuery();
}

Arguments and options can be static values, Angular signals, or functions.

postQuery = useGetPostQuery(this.postId);

postQuery = useGetPostQuery(() => this.postId());

postQuery = useGetPostQuery(
  () => this.postId(),
  () => ({ pollingInterval: this.pollingEnabled() ? 5000 : 0 }),
);

Use skipToken for conditional queries.

import { input } from '@angular/core';
import { skipToken } from 'ngrx-rtk-query';

export class PostDetailsComponent {
  postId = input<number | undefined>();

  postQuery = useGetPostQuery(() => this.postId() ?? skipToken);
}

Use selectFromResult when a component needs a selected view of cached state. It follows RTK Query semantics: only the fields you return are exposed.

selectedPostQuery = useGetPostsQuery(undefined, {
  selectFromResult: ({ data, isFetching }) => ({
    post: data?.find((post) => post.id === this.postId()),
    isFetching,
  }),
});

selectedPostQuery.post();
selectedPostQuery.isFetching();

Prefer query.isLoading() over query().isLoading when you only need one field. Fine-grained signals reduce unnecessary Angular change detection work.

Query Options and Refetching

Query, lazy query, infinite query, and mutation options can be plain objects, Angular signals, or functions. Use the reactive forms when route params, component inputs, or local signals control cache subscription behavior.

Common query options:

| Option | Use when | | --------------------------- | ---------------------------------------------------------------------------------------------- | | skip | A query should stay unsubscribed until a condition is true. | | skipToken | The query argument itself is unavailable yet. | | pollingInterval | Active subscribers should poll on an interval. | | skipPollingIfUnfocused | Polling should pause while the browser window is unfocused. | | refetchOnMountOrArgChange | A subscriber should refetch on mount or after the cached value is older than a threshold. | | refetchOnFocus | A subscriber should refetch after window focus. Requires runtime listeners to be enabled. | | refetchOnReconnect | A subscriber should refetch after network reconnect. Requires runtime listeners to be enabled. | | selectFromResult | A component only needs a selected subset of cached state. |

Manual refetch is available on query hooks:

postsQuery = useGetPostsQuery();

refresh() {
  this.postsQuery.refetch();
}

Focus and reconnect refetching use RTK Query runtime listeners. They are enabled by default in runtime providers and can be disabled with { setupListeners: false }.

Use Lazy Queries

Lazy query hooks return a trigger object instead of a tuple.

export class SearchComponent {
  searchPosts = useLazyGetPostsQuery();

  runSearch() {
    this.searchPosts(undefined).unwrap();
  }

  reset() {
    this.searchPosts.reset();
  }
}

Lazy query options can also be static values, signals, or functions.

searchPosts = useLazyGetPostsQuery(() => ({
  selectFromResult: ({ data, isFetching }) => ({
    firstPost: data?.[0],
    isFetching,
  }),
}));

Use preferCacheValue to avoid dispatching a request when the same arg is already cached.

this.searchPosts(undefined, { preferCacheValue: true });

Lazy query trigger objects expose query-state signals and lazy-query helpers such as lastArg() and reset().

Use Prefetch

Every API exposes usePrefetch(endpointName, options?). It returns a function that dispatches RTK Query prefetch for the selected endpoint.

export class PostsLinkComponent {
  prefetchPost = postsApi.usePrefetch('getPost', { ifOlderThan: 60 });

  warmPost(id: number) {
    this.prefetchPost(id);
  }
}

Use prefetch for hover, viewport, or navigation preparation. Use a query hook when the component needs subscribed state.

Use Infinite Queries

Infinite queries cache multiple pages inside one cache entry.

import { computed } from '@angular/core';
import { createApi, fetchBaseQuery } from 'ngrx-rtk-query';

type Pokemon = { id: string; name: string };

export const pokemonApi = createApi({
  reducerPath: 'pokemonApi',
  baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com/pokemon' }),
  endpoints: (build) => ({
    getPokemon: build.infiniteQuery<Pokemon[], string, number>({
      infiniteQueryOptions: {
        initialPageParam: 1,
        getNextPageParam: (_lastPage, _allPages, lastPageParam) => lastPageParam + 1,
        getPreviousPageParam: (_firstPage, _allPages, firstPageParam) =>
          firstPageParam > 1 ? firstPageParam - 1 : undefined,
      },
      query: ({ queryArg, pageParam }) => `/type/${queryArg}?page=${pageParam}`,
    }),
  }),
});

export const { useGetPokemonInfiniteQuery } = pokemonApi;

export class PokemonListComponent {
  pokemonQuery = useGetPokemonInfiniteQuery('fire');

  allResults = computed(() => this.pokemonQuery.data()?.pages.flat() ?? []);

  loadMore() {
    this.pokemonQuery.fetchNextPage();
  }
}

The hook exposes RTK Query infinite-query state including data.pages, data.pageParams, hasNextPage, hasPreviousPage, isFetchingNextPage, isFetchingPreviousPage, fetchNextPage(), and fetchPreviousPage().

Use Mutations

Mutation hooks return a trigger object with mutation-state signals.

export class AddPostComponent {
  addPost = useAddPostMutation();

  async save() {
    const createdPost = await this.addPost({ name: 'New post' }).unwrap();
    console.log(createdPost.id);
  }
}

Read mutation state directly:

this.addPost.isLoading();
this.addPost.isSuccess();
this.addPost.isError();
this.addPost.data();
this.addPost.error();

Mutation options can be static values, signals, or functions. This is useful for route-scoped fixedCacheKey values.

updatePost = useUpdatePostMutation(() => ({
  fixedCacheKey: `updatePost:${this.postId()}`,
}));

Without fixedCacheKey, mutation state is scoped to the trigger instance and exposes its own originalArgs. Use reset() to clear local mutation state.

this.addPost.reset();

Mutation hooks also support selectFromResult. Returned keys are exposed as signals while trigger methods such as unwrap() and reset() remain available on the trigger object.

Use Signal Store Readers

Use withApiState(api) to expose generated state-reader methods in an NgRx Signal Store. The API can be mounted by withApi(api), provideStoreApi(api), or provideNoopStoreApi(api).

import { computed } from '@angular/core';
import { signalStore, withComputed, withProps } from '@ngrx/signals';

import { withApi, withApiState } from 'ngrx-rtk-query/signal-store';

import { postsApi } from './posts/api';

export const PostsStore = signalStore(
  { providedIn: 'root' },
  withApi(postsApi),
  withApiState(postsApi),
  withProps((store) => ({
    selectedPostsState: store.getPostsState(),
  })),
  withComputed(({ selectedPostsState }) => ({
    selectedPostsCount: computed(() => selectedPostsState().data?.length ?? 0),
  })),
);

Reader stores can be separate from the host:

export const PostsReaderStore = signalStore(
  { providedIn: 'root' },
  withApiState(postsApi),
  withProps((store) => ({
    selectedPostsState: store.getPostsState(),
  })),
  withComputed(({ selectedPostsState }) => ({
    selectedPostsCount: computed(() => selectedPostsState().data?.length ?? 0),
  })),
);

Generated ...State() methods return the same signal as api.selectSignal(endpoint.select(...)). They capture the endpoints available when withApiState(api) is composed. If the API is later extended with api.injectEndpoints(...), compose withApiState(extendedApi) in a new store to expose the new endpoint readers.

Rules:

  • Mount each API instance once.
  • Each withApi(api) in the same Signal Store host must use a unique reducerPath.
  • Add withApiState(api) only once per API instance in a store.
  • Distinct APIs in the same store must not generate the same ...State() method name.
  • Mutation reader methods require a non-empty fixedCacheKey, because RTK Query mutation state is otherwise scoped to a trigger request.

Use Angular DI in Base Queries

fetchBaseQuery can receive a factory function. Use it when a base query needs Angular injection.

import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { inject } from '@angular/core';
import { createApi, fetchBaseQuery } from 'ngrx-rtk-query';
import { lastValueFrom } from 'rxjs';

const httpClientBaseQuery = fetchBaseQuery((http = inject(HttpClient)) => {
  return async (args) => {
    const request = typeof args === 'string' ? { url: args } : args;
    const { url, method = 'GET', body, params } = request;

    try {
      const data = await lastValueFrom(http.request(method, url, { body, params }));
      return { data };
    } catch (error) {
      const httpError =
        error instanceof HttpErrorResponse
          ? error
          : new HttpErrorResponse({ error, status: 0, statusText: 'Unknown Error' });
      return { error: { status: httpError.status, data: httpError.message } };
    }
  };
});

export const api = createApi({
  reducerPath: 'api',
  baseQuery: httpClientBaseQuery,
  endpoints: (build) => ({
    // endpoints
  }),
});

Keep boundary error shapes explicit. RTK Query expects base queries to return { data } or { error }.

Code Splitting and Lazy Routes

For the same base API, prefer RTK Query endpoint injection:

export const baseApi = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  endpoints: () => ({}),
});

export const postsApi = baseApi.injectEndpoints({
  endpoints: (build) => ({
    getPosts: build.query<Post[], void>({
      query: () => '/posts',
    }),
  }),
});

export const { useGetPostsQuery } = postsApi;

Mount the base API once near the app shell. Lazy routes can import the extended API and generated hooks for their endpoints.

Create a separate API only when the feature has a different base URL, cache identity, or runtime host requirement.

Testing

Test library behavior through public APIs:

import { provideStore } from '@ngrx/store';
import { render, screen } from '@testing-library/angular';

import { provideStoreApi } from 'ngrx-rtk-query/store';

await render(PostsListComponent, {
  providers: [provideStore(), provideStoreApi(postsApi)],
});

expect(await screen.findByRole('link', { name: /sample/i })).toBeInTheDocument();

Reset RTK Query cache between tests when a test shares an API instance:

afterEach(() => {
  postsApi.dispatch(postsApi.util.resetApiState());
});

Use the repository examples as consumer-style references:

  • examples/basic-ngrx-store
  • examples/basic-noop-store
  • examples/basic-signal-store

Examples

Run the example apps from the repository root:

pnpm dev:basic-store
pnpm dev:noop-store
pnpm dev:signal-store

Example coverage:

| Example | Demonstrates | | -------------------- | ---------------------------------------------------------------- | | basic-ngrx-store | NgRx Store provider, generated hooks, MSW-backed component tests | | basic-noop-store | Noop Store provider without NgRx Store | | basic-signal-store | withApi(api) and withApiState(api) | | *-e2e examples | Playwright runtime smoke coverage |

Troubleshooting

| Symptom | Check | | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | Provide the API is necessary | Mount the API with provideStoreApi(api), provideNoopStoreApi(api), or withApi(api) before using hooks or readers. | | NgRx middleware error | Add provideStore() before provideStoreApi(api) in NgRx Store apps. | | Query does not refetch when input changes | Pass a signal or function argument, not a one-time value. | | Conditional query fires too early | Return skipToken until the argument is ready. | | selectFromResult result is missing data or isLoading | Return every field you want to read. The selection replaces the query state shape. | | Focus or reconnect refetching does not run | Keep runtime listeners enabled, or remove { setupListeners: false } from the API provider. | | Signal Store reader does not expose an injected endpoint | Compose withApiState(extendedApi) from the extended API that includes the endpoint. | | Mutation Signal Store reader throws about fixedCacheKey | Pass the same non-empty fixedCacheKey used by the mutation hook. | | App without NgRx Store fails to resolve store imports | Import core APIs from ngrx-rtk-query/core and runtime provider from ngrx-rtk-query/noop-store. |

Maintainers

Maintainer workflow, harness, validation, and release policy live in repository docs:

Contributors ✨

Thanks goes to these wonderful people (emoji key):

This project follows the all-contributors specification.