Overview
Cache invalidation is the mechanism that keeps your data fresh after mutations. When you create, update, or delete data, ngrx-rtk-query can automatically refetch affected queries using a tag-based system.
This eliminates the need for manual refetching and ensures your UI stays in sync with the server.
Tag System
The cache invalidation system uses tags to establish relationships between queries and mutations:
Queries use providesTags to declare what data they contain
Mutations use invalidatesTags to declare what data they affect
When a mutation invalidates tags, all queries providing those tags are automatically refetched.
First, declare the tag types in your API:
import { createApi , fetchBaseQuery } from 'ngrx-rtk-query' ;
export const postsApi = createApi ({
baseQuery: fetchBaseQuery ({ baseUrl: 'http://api.localhost.com' }),
tagTypes: [ 'Posts' ], // Define available tag types
endpoints : ( build ) => ({
// Endpoints go here...
}),
});
tagTypes is an array of string identifiers. These must be defined before they can be used in providesTags or invalidatesTags.
Use providesTags in queries to declare what data they contain:
getPosts : build . query < Post [], void >({
query : () => ({ url: '/posts' }),
providesTags: [ 'Posts' ], // Simple: All posts
}),
Provide a fixed array of tags:
getPosts : build . query < Post [], void >({
query : () => '/posts' ,
providesTags: [ 'Posts' ],
}),
Provide tags based on the response data:
getPosts : build . query < Post [], void >({
query : () => '/posts' ,
providesTags : ( result ) =>
result
? [
// Individual post tags
... result . map (({ id }) => ({ type: 'Posts' , id } as const )),
// List tag
{ type: 'Posts' , id: 'LIST' },
]
: [{ type: 'Posts' , id: 'LIST' }],
}),
Use a LIST tag for the collection itself and individual ID tags for specific items. This allows fine-grained invalidation.
Tag Function Signature
providesTags : (
result : ResultType | undefined ,
error : FetchBaseQueryError | SerializedError | undefined ,
arg : QueryArg
) => TagDescription [] | TagDescription
Use invalidatesTags in mutations to declare what data they affect:
addPost : build . mutation < Post , Partial < Post >>({
query : ( body ) => ({
url: '/posts' ,
method: 'POST' ,
body ,
}),
invalidatesTags: [{ type: 'Posts' , id: 'LIST' }], // Refetch post lists
}),
Static Invalidation
Invalidate fixed tags:
addPost : build . mutation < Post , Partial < Post >>({
query : ( body ) => ({ url: '/posts' , method: 'POST' , body }),
invalidatesTags: [ 'Posts' ], // Invalidate all Posts tags
}),
Dynamic Invalidation
Invalidate tags based on the mutation result or arguments:
updatePost : build . mutation < Post , Partial < Post >>({
query : ({ id , ... body }) => ({
url: `/posts/ ${ id } ` ,
method: 'PUT' ,
body ,
}),
// Invalidate the specific post that was updated
invalidatesTags : ( result , error , { id }) => [{ type: 'Posts' , id }],
}),
Tag Invalidation Function Signature
invalidatesTags : (
result : ResultType | undefined ,
error : FetchBaseQueryError | SerializedError | undefined ,
arg : MutationArg
) => TagDescription [] | TagDescription
Tag Description Types
Tags can be described in multiple formats:
String
Object with ID
Object with String ID
Mixed
Simple string tag type: providesTags : [ 'Posts' ]
invalidatesTags : [ 'Posts' ]
Tag with specific ID: providesTags : [{ type: 'Posts' , id: 1 }]
invalidatesTags : [{ type: 'Posts' , id: 1 }]
Tag with string ID (useful for ‘LIST’, ‘ALL’, etc.): providesTags : [{ type: 'Posts' , id: 'LIST' }]
invalidatesTags : [{ type: 'Posts' , id: 'LIST' }]
Array of mixed tag descriptions: providesTags : [
'Posts' ,
{ type: 'Posts' , id: 1 },
{ type: 'Posts' , id: 'LIST' },
]
Common Patterns
List + Detail Pattern
Invalidate lists when adding items, and specific items when updating:
export const postsApi = createApi ({
baseQuery: fetchBaseQuery ({ baseUrl: 'http://api.localhost.com' }),
tagTypes: [ 'Posts' ],
endpoints : ( build ) => ({
// List query - provides LIST tag and individual item tags
getPosts: build . query < Post [], void >({
query : () => '/posts' ,
providesTags : ( result ) =>
result
? [
... result . map (({ id }) => ({ type: 'Posts' , id } as const )),
{ type: 'Posts' , id: 'LIST' },
]
: [{ type: 'Posts' , id: 'LIST' }],
}),
// Detail query - provides specific item tag
getPost: build . query < Post , number >({
query : ( id ) => `/posts/ ${ id } ` ,
providesTags : ( result , error , id ) => [{ type: 'Posts' , id }],
}),
// Add mutation - invalidates LIST (refetches list queries)
addPost: build . mutation < Post , Partial < Post >>({
query : ( body ) => ({ url: '/posts' , method: 'POST' , body }),
invalidatesTags: [{ type: 'Posts' , id: 'LIST' }],
}),
// Update mutation - invalidates specific item (refetches that item)
updatePost: build . mutation < Post , Partial < Post >>({
query : ({ id , ... body }) => ({
url: `/posts/ ${ id } ` ,
method: 'PUT' ,
body ,
}),
invalidatesTags : ( result , error , { id }) => [{ type: 'Posts' , id }],
}),
// Delete mutation - invalidates LIST (refetches list queries)
deletePost: build . mutation <{ success : boolean ; id : number }, number >({
query : ( id ) => ({ url: `/posts/ ${ id } ` , method: 'DELETE' }),
invalidatesTags: [{ type: 'Posts' , id: 'LIST' }],
}),
}),
});
Multiple Tag Types
Use multiple tag types for complex data relationships:
export const api = createApi ({
baseQuery: fetchBaseQuery ({ baseUrl: 'http://api.localhost.com' }),
tagTypes: [ 'Posts' , 'Users' , 'Comments' ],
endpoints : ( build ) => ({
getPost: build . query < Post , number >({
query : ( id ) => `/posts/ ${ id } ` ,
providesTags : ( result , error , id ) => [
{ type: 'Posts' , id },
{ type: 'Comments' , id: 'LIST' }, // Post includes comments
],
}),
addComment: build . mutation < Comment , { postId : number ; text : string }>({
query : ({ postId , text }) => ({
url: `/posts/ ${ postId } /comments` ,
method: 'POST' ,
body: { text },
}),
// Invalidate comments list for this post
invalidatesTags : ( result , error , { postId }) => [
{ type: 'Comments' , id: 'LIST' },
{ type: 'Posts' , id: postId }, // Also refetch the post
],
}),
}),
});
Conditional Invalidation
Invalidate tags only under certain conditions:
updatePost : build . mutation < Post , Partial < Post >>({
query : ({ id , ... body }) => ({
url: `/posts/ ${ id } ` ,
method: 'PUT' ,
body ,
}),
invalidatesTags : ( result , error , { id }) => {
// Only invalidate if the mutation succeeded
if ( error ) return [];
return [
{ type: 'Posts' , id },
{ type: 'Posts' , id: 'LIST' },
];
},
}),
Complete Example
Here’s a complete example showing cache invalidation in action:
// api.ts
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 }) as const ), { 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' }],
}),
updatePost: build . mutation < Post , Partial < Post >>({
query : ( data ) => {
const { id , ... body } = data ;
return {
url: `/posts/ ${ id } ` ,
method: 'PUT' ,
body ,
};
},
invalidatesTags : ( result , error , { id }) => [{ type: 'Posts' , id }],
}),
deletePost: build . mutation <{ success : boolean ; id : number }, number >({
query : ( id ) => ({
url: `/posts/ ${ id } ` ,
method: 'DELETE' ,
}),
invalidatesTags: [{ type: 'Posts' , id: 'LIST' }],
}),
}),
});
export const { useGetPostQuery , useGetPostsQuery , useAddPostMutation , useDeletePostMutation , useUpdatePostMutation } =
postsApi ;
// posts-list.component.ts
import { Component , signal } from '@angular/core' ;
import { useGetPostsQuery , useAddPostMutation } from './api' ;
@ Component ({
selector: 'app-posts-list' ,
standalone: true ,
template: `
<input
type="text"
[value]="postName()"
(input)="postName.set($any($event.target).value)" />
<button
[disabled]="!postName() || addPost.isLoading()"
(click)="addNewPost()">
{{ addPost.isLoading() ? 'Adding...' : 'Add Post' }}
</button>
@if (postsQuery.data(); as posts) {
@for (post of posts; track post.id) {
<li>{{ post.name }}</li>
}
}
` ,
})
export class PostsListComponent {
// This query will automatically refetch when addPost() succeeds
postsQuery = useGetPostsQuery ();
addPost = useAddPostMutation ();
postName = signal ( '' );
addNewPost () {
this . addPost ({ name: this . postName () })
. unwrap ()
. then (() => {
this . postName . set ( '' );
// No manual refetch needed! postsQuery auto-refetches due to tag invalidation
});
}
}
Notice how we don’t need to manually call postsQuery.refetch(). The tag system handles refetching automatically.
Manual Cache Invalidation
Sometimes you need to invalidate cache manually without a mutation:
import { inject } from '@angular/core' ;
import { Store } from '@ngrx/store' ;
import { postsApi } from './api' ;
export class MyComponent {
#store = inject ( Store );
invalidateAllPosts () {
this . #store . dispatch (
postsApi . util . invalidateTags ([{ type: 'Posts' , id: 'LIST' }])
);
}
invalidateSpecificPost ( id : number ) {
this . #store . dispatch (
postsApi . util . invalidateTags ([{ type: 'Posts' , id }])
);
}
}
TypeScript Type Signatures
Tag Description Types
type TagDescription < TagType extends string = string > =
| TagType
| { type : TagType ; id ?: string | number };
type ProvidesTags < ResultType , QueryArg , TagType extends string > =
| TagDescription < TagType >[]
| TagDescription < TagType >
| (( result : ResultType | undefined , error : FetchBaseQueryError | SerializedError | undefined , arg : QueryArg ) =>
TagDescription < TagType >[] | TagDescription < TagType >);
type InvalidatesTags < ResultType , QueryArg , TagType extends string > =
| TagDescription < TagType >[]
| TagDescription < TagType >
| (( result : ResultType | undefined , error : FetchBaseQueryError | SerializedError | undefined , arg : QueryArg ) =>
TagDescription < TagType >[] | TagDescription < TagType >);
Best Practices
Use LIST + Individual ID Tags
Invalidate LIST on Create/Delete
Invalidate Specific IDs on Update
Use as const for Type Safety
// ✅ Good - Enables fine-grained invalidation
getPosts : build . query < Post [], void >({
query : () => '/posts' ,
providesTags : ( result ) =>
result
? [
... result . map (({ id }) => ({ type: 'Posts' , id }) as const ),
{ type: 'Posts' , id: 'LIST' },
]
: [{ type: 'Posts' , id: 'LIST' }],
}),