Overview
Lazy queries allow you to trigger data fetching on demand, rather than automatically on component mount. This is useful for:
Loading data in response to user actions (button clicks, form submissions)
Fetching nested or relational data
Implementing search or autocomplete features
Deferring expensive queries until needed
Defining a Lazy Query
Lazy query hooks are auto-generated from the same query endpoints as regular queries:
import { createApi , fetchBaseQuery } from 'ngrx-rtk-query' ;
export interface Post {
id : number ;
name : string ;
}
export const postsApi = createApi ({
baseQuery: fetchBaseQuery ({ baseUrl: 'http://api.localhost.com' }),
endpoints : ( build ) => ({
getPost: build . query < Post , number >({
query : ( id ) => `/posts/ ${ id } ` ,
}),
}),
});
// Both hooks are generated from the same endpoint
export const {
useGetPostQuery , // Regular query (auto-fetches)
useLazyGetPostQuery , // Lazy query (manual trigger)
} = postsApi ;
Every query endpoint automatically generates both a useXxxQuery and a useLazyXxxQuery hook.
Basic Usage
A lazy query hook returns a trigger function with query state attached:
import { Component } from '@angular/core' ;
import { useLazyGetPostQuery } from './api' ;
@ Component ({
selector: 'app-post-loader' ,
standalone: true ,
template: `
<button (click)="loadPost(1)">Load Post</button>
@if (postQuery.isLoading()) {
<p>Loading...</p>
}
@if (postQuery.data(); as post) {
<div>{{ post.name }}</div>
}
` ,
})
export class PostLoaderComponent {
postQuery = useLazyGetPostQuery ();
loadPost ( id : number ) {
this . postQuery ( id );
}
}
Lazy Query Structure
The lazy query hook returns a trigger function with signals attached:
const trigger = useLazyGetPostQuery ();
// Trigger function - call to start the query
trigger ( postId );
// Signal properties - access like regular query
trigger . data ();
trigger . isLoading ();
trigger . isError ();
trigger . error ();
trigger . lastArg (); // The last argument passed to trigger
// Utility methods
trigger . reset (); // Clear query cache
Unlike regular queries, lazy queries return the trigger function itself, with signals attached as properties.
Query State Properties
Lazy queries provide the same state properties as regular queries:
Property Type Description data()T | undefinedThe latest successfully fetched data currentData()T | undefinedThe data for the current query args isLoading()booleantrue during initial fetch with no cached dataisFetching()booleantrue whenever a request is in flightisSuccess()booleantrue when data is availableisError()booleantrue if the query resulted in an errorerror()SerializedError | FetchBaseQueryErrorError object if query failed lastArg()ArgType | undefinedThe last argument passed to the trigger reset()() => voidClear the query cache
Trigger Options
The trigger function accepts an optional second argument for request options:
export class PostLoaderComponent {
postQuery = useLazyGetPostQuery ();
loadPost ( id : number ) {
this . postQuery ( id , {
preferCacheValue: true // Use cached value if available
});
}
}
preferCacheValue
When preferCacheValue: true, the query will use cached data if available instead of making a new request:
loadPost ( id : number ) {
// First call: makes request
this . postQuery ( id , { preferCacheValue: true });
// Second call with same id: uses cache
this . postQuery ( id , { preferCacheValue: true });
}
preferCacheValue defaults to false. Set it to true to prevent duplicate requests.
Unwrapping Promises
Use .unwrap() to access the resolved data or handle errors:
export class PostLoaderComponent {
postQuery = useLazyGetPostQuery ();
loadPost ( id : number ) {
this . postQuery ( id )
. unwrap ()
. then (( post ) => {
console . log ( 'Post loaded:' , post );
})
. catch (( error ) => {
console . error ( 'Failed to load post:' , error );
});
}
}
Async/Await Pattern
async loadPost ( id : number ) {
try {
const post = await this . postQuery ( id ). unwrap ();
console . log ( 'Post loaded:' , post );
} catch ( error ) {
console . error ( 'Failed to load post:' , error );
}
}
Lazy Query Options
Configure query behavior with options:
export class PostLoaderComponent {
postQuery = useLazyGetPostQuery ({
pollingInterval: 5000 , // Poll every 5 seconds after trigger
refetchOnFocus: true , // Refetch when window gains focus
refetchOnReconnect: true , // Refetch when connection restored
selectFromResult : ({ data , isLoading }) => ({ data , isLoading }),
});
}
Signal or Function Options
Options can be signals or functions:
export class PostLoaderComponent {
enablePolling = signal ( false );
postQuery = useLazyGetPostQuery (
() => ({
pollingInterval: this . enablePolling () ? 5000 : 0 ,
})
);
}
Resetting Query State
Use the reset() method to clear the query cache:
@ Component ({
template: `
<button (click)="loadPost(1)">Load Post</button>
<button (click)="postQuery.reset()">Clear</button>
@if (postQuery.data(); as post) {
<div>{{ post.name }}</div>
}
`
})
export class PostLoaderComponent {
postQuery = useLazyGetPostQuery ();
loadPost ( id : number ) {
this . postQuery ( id );
}
}
Calling reset() removes the query from the cache. The next trigger will make a fresh request.
Common Use Cases
User-Triggered Data Loading
@ Component ({
template: `
<button (click)="loadUserDetails()">Load My Details</button>
@if (userQuery.data(); as user) {
<div>{{ user.name }}</div>
}
`
})
export class UserProfileComponent {
userQuery = useLazyGetUserQuery ();
userId = signal ( 1 );
loadUserDetails () {
this . userQuery ( this . userId ());
}
}
Nested/Relational Data
import { Component , OnInit , input } from '@angular/core' ;
export interface Character {
id : number ;
name : string ;
currentLocation : number ;
}
@ Component ({
template: `
<h2>{{ character().name }}</h2>
@if (locationQuery.data(); as location) {
<p>Location: {{ location.name }}</p>
}
`
})
export class CharacterCardComponent implements OnInit {
character = input . required < Character >();
locationQuery = useLazyGetLocationQuery ();
ngOnInit () {
// Load location when component initializes
this . locationQuery (
this . character (). currentLocation ,
{ preferCacheValue: true }
);
}
}
For relational data, consider using regular queries with skipToken for a more declarative approach: locationQuery = useGetLocationQuery (
() => this . character ()?. currentLocation ?? skipToken
);
Search/Autocomplete
@ Component ({
template: `
<input
type="text"
(input)="search($event)"
placeholder="Search posts..." />
@if (searchQuery.isFetching()) {
<p>Searching...</p>
}
@if (searchQuery.data(); as results) {
@for (result of results; track result.id) {
<div>{{ result.name }}</div>
}
}
`
})
export class SearchComponent {
searchQuery = useLazySearchPostsQuery ();
search ( event : Event ) {
const query = ( event . target as HTMLInputElement ). value ;
if ( query . length > 2 ) {
this . searchQuery ( query );
}
}
}
TypeScript Type Signatures
Lazy Query Hook Type
type UseLazyQuery < ResultType , ArgType > = (
options ?: UseQueryOptions < ResultType > | Signal < UseQueryOptions < ResultType >> | (() => UseQueryOptions < ResultType >)
) => LazyQueryTrigger < ResultType , ArgType >;
type LazyQueryTrigger < ResultType , ArgType > = {
// Trigger function
( arg : ArgType , options ?: { preferCacheValue ?: boolean }) : QueryActionCreatorResult < ResultType >;
// Signal properties
data : Signal < ResultType | undefined >;
currentData : Signal < ResultType | undefined >;
error : Signal < SerializedError | FetchBaseQueryError | undefined >;
isUninitialized : Signal < boolean >;
isLoading : Signal < boolean >;
isFetching : Signal < boolean >;
isSuccess : Signal < boolean >;
isError : Signal < boolean >;
// Utility properties
lastArg : Signal < ArgType | undefined >;
reset : () => void ;
};
Best Practices
Use preferCacheValue in ngOnInit
Handle Errors with unwrap
Consider Regular Queries for Auto-Fetch
Reset Between Different Contexts
// ✅ Good - Prevents duplicate requests
export class CharacterCardComponent implements OnInit {
character = input . required < Character >();
locationQuery = useLazyGetLocationQuery ();
ngOnInit () {
this . locationQuery (
this . character (). currentLocation ,
{ preferCacheValue: true } // Use cache if available
);
}
}
Comparison: Regular vs Lazy Queries
Feature Regular Query Lazy Query Fetches on mount ✅ Yes ❌ No Manual trigger Via refetch() Via trigger function Return type Signal object Trigger function with signals Use case Declarative, auto-fetch Imperative, on-demand lastArg property❌ No ✅ Yes reset() method❌ No ✅ Yes