Using HttpContext to Configure httpResource Requests

Angular's httpResource simplifies data fetching, and HttpContext allows you to pass metadata to HTTP interceptors. This combination provides a powerful way to manage HTTP requests, especially when you need to modify them dynamically. This blog post will explore how to integrate httpResource and HttpContext. We'll focus on a practical example: setting the responseType of an HTTP request based on context. The Problem It Solves Sometimes, you need to provide additional information to your HTTP requests that isn't part of the request body or headers. For example, you might need to tell an interceptor how to handle the response, such as specifying the responseType. HttpContext lets you attach this metadata to the request, and interceptors can then use this metadata to modify the request before it's sent. This is particularly useful when working with binary data, where you might need to specify whether you want an ArrayBuffer or a Blob. First, let's update Angular to the latest version. As of this writing, the Angular version is 19.2.6. ng update @angular/core @angular/cli In this demo, we'll create components that download sound and image files in arraybuffer and blob types. The HttpResourceRequest options will intentionally omit the responseType property to demonstrate the usage of HttpContextToken. Without setting the responseType, the requests would result in an error: "Http failure during parsing for ''". We'll use an HTTP interceptor to set this responseType based on the context dynamically. Defining HttpContextToken First, we define a type and then create an HttpContextToken: export type BinaryResponseType = 'arraybuffer' | 'blob' | ''; export const RESPONSE_TYPE = new HttpContextToken(() => 'arraybuffer'); Here, BinaryResponseType defines the possible values for the response type. RESPONSE_TYPE is an HttpContextToken that holds the response type. The token is initialized with a default value of 'arraybuffer'. This default value will be used if no specific responseType is provided in the request context. Creating an HTTP Interceptor Next, we create an HTTP interceptor that reads the responseType from the HttpContext and modifies the request accordingly: export function requestInterceptor(req: HttpRequest, next: HttpHandlerFn) { const responseType = req.context.get(RESPONSE_TYPE); if (responseType !== '') { const reqCloned = req.clone({ responseType }); return next(reqCloned); } return next(req); } The requestInterceptor function intercepts outgoing HTTP requests. It retrieves the responseType from the request's HttpContext using req.context.get(RESPONSE_TYPE). If a responseType is present (i.e., not an empty string), the interceptor clones the original request using req.clone() and sets the responseType property. This cloned request, with the modified responseType, is then passed to the next handler in the chain (or, if this is the last interceptor, to the actual HTTP request). If no responseType is found in the context, the original request is passed along unmodified. Configuring the Interceptors We then configure the interceptor in our application's configuration: export const appConfig: ApplicationConfig = { providers: [ provideHttpClient( withInterceptors([requestInterceptor]) ), ], }; bootstrapApplication(App, appConfig); In the appConfig, we use provideHttpClient to configure the Angular HttpClient. The withInterceptors function takes an array of interceptor functions. We include our requestInterceptor in this array. This ensures that our interceptor is applied to all the HTTP requests. The bootstrapApplication function is used to bootstrap the main component of the Angular application (App) with the configured appConfig. Utility Functions Here's a utility function to create a URL from the binary data: export function createURLFromBinary(responseType: BinaryResponseType, value: unknown | undefined) { if (value) { const data = responseType === 'blob' ? value as Blob : new Blob([value as ArrayBuffer]); return URL.createObjectURL(data); } return undefined; } The createURLFromBinary function takes the responseType and the binary data (value) as input. It converts the value (which can be either an ArrayBuffer or a Blob) into a Blob object. It then uses URL.createObjectURL() to create a unique URL that points to this Blob in memory. This URL can then be used as the source for an audio or img element. If the value is undefined, the function returns undefined. Creating HTTP Requests with Context This function creates the HttpResourceRequest with the specified context: export function makeHttpRequest(url: string, responseType: BinaryResponseType) { const defaultOptions = { url, reportProgress: true, method: 'GET', }; return responseType ? { ...defaultOptions, context: new HttpContext().set

Apr 13, 2025 - 09:19
 0
Using HttpContext to Configure httpResource Requests

Angular's httpResource simplifies data fetching, and HttpContext allows you to pass metadata to HTTP interceptors. This combination provides a powerful way to manage HTTP requests, especially when you need to modify them dynamically.

This blog post will explore how to integrate httpResource and HttpContext. We'll focus on a practical example: setting the responseType of an HTTP request based on context.

The Problem It Solves

Sometimes, you need to provide additional information to your HTTP requests that isn't part of the request body or headers. For example, you might need to tell an interceptor how to handle the response, such as specifying the responseType. HttpContext lets you attach this metadata to the request, and interceptors can then use this metadata to modify the request before it's sent. This is particularly useful when working with binary data, where you might need to specify whether you want an ArrayBuffer or a Blob.

First, let's update Angular to the latest version. As of this writing, the Angular version is 19.2.6.

ng update @angular/core @angular/cli

In this demo, we'll create components that download sound and image files in arraybuffer and blob types. The HttpResourceRequest options will intentionally omit the responseType property to demonstrate the usage of HttpContextToken. Without setting the responseType, the requests would result in an error: "Http failure during parsing for ''". We'll use an HTTP interceptor to set this responseType based on the context dynamically.

Defining HttpContextToken

First, we define a type and then create an HttpContextToken:

export type BinaryResponseType = 'arraybuffer' | 'blob' | '';
export const RESPONSE_TYPE = new HttpContextToken<BinaryResponseType>(() => 'arraybuffer');

Here, BinaryResponseType defines the possible values for the response type. RESPONSE_TYPE is an HttpContextToken that holds the response type. The token is initialized with a default value of 'arraybuffer'. This default value will be used if no specific responseType is provided in the request context.

Creating an HTTP Interceptor

Next, we create an HTTP interceptor that reads the responseType from the HttpContext and modifies the request accordingly:

export function requestInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn) {
  const responseType = req.context.get(RESPONSE_TYPE);

  if (responseType !== '') {
    const reqCloned = req.clone({ responseType });
    return next(reqCloned);
  }

  return next(req);
}

The requestInterceptor function intercepts outgoing HTTP requests. It retrieves the responseType from the request's HttpContext using req.context.get(RESPONSE_TYPE). If a responseType is present (i.e., not an empty string), the interceptor clones the original request using req.clone() and sets the responseType property. This cloned request, with the modified responseType, is then passed to the next handler in the chain (or, if this is the last interceptor, to the actual HTTP request). If no responseType is found in the context, the original request is passed along unmodified.

Configuring the Interceptors

We then configure the interceptor in our application's configuration:

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(
      withInterceptors([requestInterceptor])
    ),
  ],
};

bootstrapApplication(App, appConfig);

In the appConfig, we use provideHttpClient to configure the Angular HttpClient. The withInterceptors function takes an array of interceptor functions. We include our requestInterceptor in this array. This ensures that our interceptor is applied to all the HTTP requests. The bootstrapApplication function is used to bootstrap the main component of the Angular application (App) with the configured appConfig.

Utility Functions

Here's a utility function to create a URL from the binary data:

export function createURLFromBinary(responseType: BinaryResponseType, value: unknown | undefined) {
  if (value) {
    const data = responseType === 'blob' ?
      value as Blob :
      new Blob([value as ArrayBuffer]);

    return URL.createObjectURL(data);
  }

  return undefined;
}

The createURLFromBinary function takes the responseType and the binary data (value) as input. It converts the value (which can be either an ArrayBuffer or a Blob) into a Blob object. It then uses URL.createObjectURL() to create a unique URL that points to this Blob in memory. This URL can then be used as the source for an audio or img element. If the value is undefined, the function returns undefined.

Creating HTTP Requests with Context

This function creates the HttpResourceRequest with the specified context:

export function makeHttpRequest(url: string, responseType: BinaryResponseType) {
  const defaultOptions = {
    url,
    reportProgress: true,
    method: 'GET',
  };

  return responseType ? {
    ...defaultOptions,
    context: new HttpContext().set(RESPONSE_TYPE, responseType),
  } : defaultOptions;
}

The makeHttpRequest function constructs the options object needed for an httpResource request. It takes the URL and the desired responseType as arguments. If a responseType is provided, it creates a new HttpContext and sets the RESPONSE_TYPE token to the given responseType. This HttpContext is then included in the request options. If no responseType is provided, it returns the default options without any context.

Retrieving Binary Data with httpResource

Here's how we use these components:

const PIKACHU_OGG_URL = 'https://raw.githubusercontent.com/PokeAPI/cries/main/cries/pokemon/latest/25.ogg';

@Component({
  selector: 'app-resource-pokemon-audio',
  template: `
    
@if (audioSrc()) { }
`
, changeDetection: ChangeDetectionStrategy.OnPush, }) export class HttpResourcePokemonAudioComponent { audioUrl = PIKACHU_OGG_URL; audioSignal = signal(''); responseType = input<BinaryResponseType>(''); audioResourceRef = httpResource( () => this.audioSignal() ? makeHttpRequest(this.audioSignal(), this.responseType()) : undefined ); audioSrc = computed(() => { const value = this.audioResourceRef.hasValue() ? this.audioResourceRef.value() : undefined; return createURLFromBinary(this.responseType(), value); }); }
const PIKACHU_IMAGE_URL = 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/shiny/25.png';

@Component({
  selector: 'app-resource-pokemon',
  template: `
    
@if (imgSrc()) { }
`
, changeDetection: ChangeDetectionStrategy.OnPush, }) export class HttpResourcePokemonComponent { imageUrl = PIKACHU_IMAGE_URL; imageSignal = signal(''); responseType = input<BinaryResponseType>(''); imgResourceRef = httpResource( () => this.imageSignal() ? makeHttpRequest(this.imageSignal(), this.responseType()) : undefined ); imgSrc = computed(() => { const value = this.imgResourceRef.hasValue() ? this.imgResourceRef.value() : undefined; return createURLFromBinary(this.responseType(), value); }); }
@Component({
  selector: 'app-root',
  imports: [
    HttpResourcePokemonAudioComponent,
    HttpResourcePokemonComponent,
  ],
  template: `
    
Retrieve Pikachu ArrayBuffer Retrieve Pikachu Blob
Retrieve ArrayBuffer Pikachu Cry Retrieve Blob Pikachu Cry
`
, changeDetection: ChangeDetectionStrategy.OnPush, }) export class App { }

In this code, the App component serves as the root component of the Angular application. Its primary responsibility is to integrate and render the HttpResourcePokemonComponent and HttpResourcePokemonAudioComponent.

Let's break down the App component:

  • Imports: The App component imports HttpResourcePokemonAudioComponent and HttpResourcePokemonComponent.
  • Template: The template of the App component defines the structure of what will be rendered in the application's main view.
    • It includes two instances of HttpResourcePokemonComponent:
      • The first one uses the default responseType (which is 'arraybuffer' as defined in the RESPONSE_TYPE token).
      • The second one explicitly sets the responseType to 'blob'.
    • It also includes two instances of HttpResourcePokemonAudioComponent:
      • The first one uses the default responseType ('arraybuffer').
      • The second one sets the responseType to 'blob'.
  • app-root Selector: The App component is typically associated with the selector in the index.html file. This means that when the Angular application is loaded, the App component is the first component to be rendered in the browser.

Benefits

Storing the response type in the HttpContext and overwriting it in an interceptor offers several advantages:

  • Decoupling: The component making the HTTP request doesn't need to know how the request is being modified. It simply sets the desired responseType in the context.
  • Centralized Logic: The interceptor centralizes the logic for handling the responseType. This keeps your components cleaner and easier to maintain.
  • Dynamic Configuration: You can dynamically determine the responseType at runtime based on application state or user input.
  • Reusability: The interceptor can be reused across multiple components and services.

Conclusion

By combining httpResource with HttpContext and HTTP interceptors, you can create a flexible and maintainable way to handle HTTP requests with dynamic metadata. This approach allows you to keep your components focused on their core logic while centralizing request modification logic in interceptors. This pattern is especially useful for scenarios like handling different response types, adding custom headers, or implementing authentication.

Resources