Integrating Angular's httpResource with HttpInterceptors
Angular's httpResource provides a powerful and convenient way to work with HTTP requests, especially when dealing with resources like blobs and array buffers. However, real-world applications often require more than just basic data fetching. You might need to log request durations, modify request headers, or handle errors globally. That's where HttpInterceptors come in. HttpInterceptors allow you to intercept and modify HTTP requests and responses, giving you a centralized way to handle cross-cutting concerns. This blog post will explore how to integrate httpResource with HttpInterceptors. Since httpResource uses Angular's HttpClient under the hood, HttpInterceptors can seamlessly intercept and modify the requests made by httpResource, enabling you to add custom logic to your data-fetching process. Before we dive into the code, let's update Angular to the latest version. As of this writing, the latest version is 19.2.5. You can update your Angular CLI and Core packages by running the following command: ng update @angular/core @angular/cli Example: Using HttpInterceptors with httpResource In this example, we'll demonstrate two use cases: Logging Request Duration: We'll create an interceptor that measures the time it takes for a request to complete and logs the duration. Modifying Requests: We'll create an interceptor that modifies GET requests to remove the request body, addressing a potential issue where httpResource might inadvertently include a body in GET requests (which are not supposed to have a body). Here's the code: 1) Create HTTP Interceptors First, let's define our interceptors: export const elapsed = signal(0); export const loggingInterceptor: HttpInterceptorFn = ( req: HttpRequest, next: HttpHandlerFn ) => { const startTime = Date.now(); return next(req).pipe( finalize(() => { const endTime = Date.now(); elapsed.set(endTime - startTime); console.log(`Round trip time is ${elapsed()} milliseconds.`); }) ); }; export const requestInterceptor: HttpInterceptorFn = ( req: HttpRequest, next: HttpHandlerFn ) => { const method = req.method || 'GET'; // GET method does not have body if (method === 'GET' && req.body) { return next(req.clone({ body: null })); } else { return next(req); } }; loggingInterceptor: This interceptor measures the time taken for each HTTP request. It uses the finalize operator to ensure the timing logic runs regardless of whether the request succeeds or fails. The elapsed time is stored in a signal and also logged to the console. requestInterceptor: This interceptor addresses a potential issue with httpResource where a body might be included in a GET request. Since GET requests should not have a body, this interceptor clones the request and removes the body if the method is GET. This prevents server errors. Without this interceptor, the httpResource requests with a body in a GET request would result in an error message similar to: "Http failure response for '': 0 Unknown Error". 2) Configure the Interceptors Next, we need to configure our application to use these interceptors. We do this when we provide the HttpClient: export const appConfig: ApplicationConfig = { providers: [ provideHttpClient( withInterceptors([loggingInterceptor, requestInterceptor]) ), ], }; bootstrapApplication(AppComponent, appConfig); appConfig: The provideHttpClient function is used to configure the HttpClient with the specified interceptors. The withInterceptors function takes an array of interceptor functions. 3) Using httpResource Now, let's see how httpResource is used within components. We'll create two components: one that fetches a blob (an audio file) and another that fetches an array buffer (an image). We'll also simulate an error by initially including a body in the GET request. 3.1) Using httpResource.blob in HttpResourceBlobComponent import { Component, ChangeDetectionStrategy, computed, signal } from '@angular/core'; import { httpResource, HttpResourceRef } from '@angular/common/http'; export function makeResourceRefStatus( resourceRef: HttpResourceRef ) { return { progress: computed(() => { if (resourceRef.progress()) { const progress = resourceRef.progress(); return `${progress?.loaded}/${progress?.total}`; } return ''; }), error: computed(() => resourceRef.error() ? (resourceRef.error() as Error) : undefined ), }; } makeResourceRefStatus: This is a helper function to construct the progress and error computed signals. const PIKACHU_OGG_URL = 'https://raw.githubusercontent.com/PokeAPI/cries/main/cries/pokemon/latest/25.ogg'; @Component({ selector: 'app-resource-blob', templateUrl: './httpresource-blob.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) export class HttpResourceBlobComponent { audioUrl = PIKACHU_OGG_URL; audioSignal = signal(''); audioResource

Angular's httpResource
provides a powerful and convenient way to work with HTTP requests, especially when dealing with resources like blobs and array buffers.
However, real-world applications often require more than just basic data fetching. You might need to log request durations, modify request headers, or handle errors globally. That's where HttpInterceptors
come in. HttpInterceptors
allow you to intercept and modify HTTP requests and responses, giving you a centralized way to handle cross-cutting concerns.
This blog post will explore how to integrate httpResource
with HttpInterceptors
. Since httpResource
uses Angular's HttpClient
under the hood, HttpInterceptors
can seamlessly intercept and modify the requests made by httpResource
, enabling you to add custom logic to your data-fetching process.
Before we dive into the code, let's update Angular to the latest version. As of this writing, the latest version is 19.2.5. You can update your Angular CLI and Core packages by running the following command:
ng update @angular/core @angular/cli
Example: Using HttpInterceptors
with httpResource
In this example, we'll demonstrate two use cases:
- Logging Request Duration: We'll create an interceptor that measures the time it takes for a request to complete and logs the duration.
- Modifying Requests: We'll create an interceptor that modifies GET requests to remove the request body, addressing a potential issue where
httpResource
might inadvertently include a body in GET requests (which are not supposed to have a body).
Here's the code:
1) Create HTTP Interceptors
First, let's define our interceptors:
export const elapsed = signal(0);
export const loggingInterceptor: HttpInterceptorFn = (
req: HttpRequest<unknown>,
next: HttpHandlerFn
) => {
const startTime = Date.now();
return next(req).pipe(
finalize(() => {
const endTime = Date.now();
elapsed.set(endTime - startTime);
console.log(`Round trip time is ${elapsed()} milliseconds.`);
})
);
};
export const requestInterceptor: HttpInterceptorFn = (
req: HttpRequest<unknown>,
next: HttpHandlerFn
) => {
const method = req.method || 'GET';
// GET method does not have body
if (method === 'GET' && req.body) {
return next(req.clone({ body: null }));
} else {
return next(req);
}
};
loggingInterceptor: This interceptor measures the time taken for each HTTP request. It uses the
finalize
operator to ensure the timing logic runs regardless of whether the request succeeds or fails. The elapsed time is stored in a signal and also logged to the console.requestInterceptor: This interceptor addresses a potential issue with
httpResource
where a body might be included in a GET request. Since GET requests should not have a body, this interceptor clones the request and removes the body if the method is GET. This prevents server errors. Without this interceptor, thehttpResource
requests with a body in a GET request would result in an error message similar to: "Http failure response for '': 0 Unknown Error".
2) Configure the Interceptors
Next, we need to configure our application to use these interceptors. We do this when we provide the HttpClient
:
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withInterceptors([loggingInterceptor, requestInterceptor])
),
],
};
bootstrapApplication(AppComponent, appConfig);
- appConfig: The provideHttpClient function is used to configure the HttpClient with the specified interceptors. The withInterceptors function takes an array of interceptor functions.
3) Using httpResource
Now, let's see how httpResource
is used within components. We'll create two components: one that fetches a blob (an audio file) and another that fetches an array buffer (an image). We'll also simulate an error by initially including a body in the GET request.
3.1) Using httpResource.blob in HttpResourceBlobComponent
import { Component, ChangeDetectionStrategy, computed, signal } from '@angular/core';
import { httpResource, HttpResourceRef } from '@angular/common/http';
export function makeResourceRefStatus(
resourceRef: HttpResourceRef<unknown | undefined>
) {
return {
progress: computed(() => {
if (resourceRef.progress()) {
const progress = resourceRef.progress();
return `${progress?.loaded}/${progress?.total}`;
}
return '';
}),
error: computed(() =>
resourceRef.error() ? (resourceRef.error() as Error) : undefined
),
};
}
- makeResourceRefStatus: This is a helper function to construct the progress and error computed signals.
const PIKACHU_OGG_URL = 'https://raw.githubusercontent.com/PokeAPI/cries/main/cries/pokemon/latest/25.ogg';
@Component({
selector: 'app-resource-blob',
templateUrl: './httpresource-blob.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class HttpResourceBlobComponent {
audioUrl = PIKACHU_OGG_URL;
audioSignal = signal('');
audioResource = httpResource.blob(() =>
this.audioSignal()
? {
url: this.audioSignal(),
reportProgress: true,
body: { x: 'bad blob data' }, // Intentionally incorrect body for GET
}
: undefined
);
resourceRefStatus = makeResourceRefStatus(this.audioResource);
audioProgress = this.resourceRefStatus.progress;
audioError = this.resourceRefStatus.error;
blobURL = computed(() =>
this.audioResource.hasValue() ? URL.createObjectURL(this.audioResource.value()) : undefined
);
}
{{ `Url: ${audioSignal()}` }}
{{ `Progress: ${audioProgress()}` }}
@if (audioError()) {
Error: {{ audioError()?.message }}
} @else if (blobURL()) {
Listen to Pikachu:
}
- HttpResourceBlobComponent: This component uses httpResource.blob to fetch an OGG file from a URL and display the results. We've intentionally added a body property to the
HttpResourceRequest
options, which is incorrect for a GET request. This will cause an error if the requestInterceptor is not present. The component also displays the progress of the download and any errors that occur.- blobURL is a computed signal that creates a URL object from the Blob, then assigns it to the audio element's
src
attribute.
- blobURL is a computed signal that creates a URL object from the Blob, then assigns it to the audio element's
3.2) Using httpResource.arraybuffer in HttpResourceArrayBufferComponent
const PIKACHU_IMAGE_URL = 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/shiny/25.png';
@Component({
selector: 'app-resource-array-buffer',
templateUrl: './httpresource-arraybuffer.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class HttpResourceArrayBufferComponent {
imageUrl = PIKACHU_IMAGE_URL;
imageSignal = signal('');
imgResource = httpResource.arrayBuffer(() =>
this.imageSignal()
? {
url: this.imageSignal(),
reportProgress: true,
method: 'GET',
body: { x: 'bad arraybuffer data' }, // Intentionally incorrect body for GET
}
: undefined
);
resourceRefStatus = makeResourceRefStatus(this.imgResource);
imgProgress = this.resourceRefStatus.progress;
imgError = this.resourceRefStatus.error;
bufferedImage = computed(() => {
return this.imgResource.hasValue()
? URL.createObjectURL(new Blob([this.imgResource.value()]))
: undefined;
});
}
{{ `Url: ${imageSignal()}` }}
{{ `Progress: ${imgProgress()}`}}
@if (imgError()) {
Error: {{ imgError()?.message }}
} @else if (bufferedImage()) {
Pikachu:
[src]="bufferedImage()" />
}
- HttpResourceArrayBufferComponent: This component uses httpResource.arrayBuffer to fetch a PNG file from the URL and display the result. We have intentionally added a body property to the
HttpResourceRequest
options, which is incorrect for a GET request. This will cause an error if the requestInterceptor is not present. The component also displays the progress of the download and any errors that occur.- bufferedImage is a computed signal that creates a URL object from the ArrayBuffer, then assigns it to the img element's
src
attribute.
- bufferedImage is a computed signal that creates a URL object from the ArrayBuffer, then assigns it to the img element's
4) See Components in Action in AppComponent
The AppComponent
ties the two components together to show them in action.
@Component({
selector: 'app-root',
imports: [HttpResourceBlobComponent, HttpResourceArrayBufferComponent],
templateUrl: './app.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent { }
// app.component.html
/>
/>
/>
/>
- This is the main component that uses the HttpResourceBlobComponent and HttpResourceArrayBufferComponent. It displays a horizontal rule (
) separating them.
Benefits
- Centralized Request Handling: HttpInterceptors provide a centralized way to handle cross-cutting concerns like logging, error handling, and request modification. This keeps your component code clean and focused on data display.
- Modularity: Interceptors are modular and can be easily added or removed from your application's HTTP pipeline.
- Reusability: Interceptors can be reused across multiple services and components, reducing code duplication.
- Simplified httpResource Usage: Interceptors can handle complexities like modifying requests, allowing you to use httpResource with cleaner configuration options within your components.
- Conclusion
- Integrating HttpInterceptors with httpResource sub-constructors provides a robust and flexible way to manage HTTP requests in Angular applications. Interceptors enable you to handle cross-cutting concerns effectively, while httpResource simplifies data fetching for specific resource types. By combining these two powerful features, you can create efficient, maintainable, and error-resistant data-driven applications.
Resources
- The PR relevant to httpResource: https://github.com/angular/angular/pull/59876
- The HttpResource documentation: https://angular.dev/api/common/http/httpResource
- The HttpInterceptor documentation: https://angular.dev/api/common/http/HttpInterceptor
- Angular interceptors guide: https://angular.dev/guide/http/interceptors
- Stackblitz Demo: https://stackblitz.com/edit/stackblitz-starters-zv8xwcnu?file=src%2Fmain.ts