On 2025-02-14 Alex Rickabaugh showed us the new httpResource over at Tech Stack Nation. It will land in Angular v19.2.0.

Beware

  • It’s experimental.
  • Expect changes.
  • This is not covered by official documentation, it’s what I learned from the presentation and pull request.

Usecase

Fetch data from an HTTP backend.

Note the emphasis of fetch. It’s not meant for mutating data in the backend. Because an HttpResourceRef<T> will be bound to the components/service lifecycle, ongoing requests will be cancelled, if the component/service gets destroyed. Also when a new request is emitted, an ongoing HTTP call will be cancelled (switchMap behaviour). Therefore the default HTTP method is GET, but other methods are possible, if you need POST or PUT for fetching data (looking at you, GraphQL 😎).

Incarnations

Imaging having a component and defining a field inside it.

@Component({...})
export class UserProfile {
  protected readonly userProfile = httpResource(...);
}

In the template you can use the resource as usual.

@if (userProfile.hasValue()) {
  @let up = userProfile.value();
  
  <!-- and display other things from the resource -->
} @else if (userProfile.isLoading()) {
  <fancy-spinner />
} @else if (userProfile.error()) {
  <fancy-error [error]="userProfile.error()" />
}

The request fires as soon as the httpResource is instantiated (or to be more precise, when the effect behind the resource is scheduled). It will need an injection context to get the HttpClient (interceptors will work!). As usual you can provide an injector if you are running outside an injection context.

The HttpResourceRef<T> is a Resource<T>, so you have usual suspects regarding signals.

  • .value()
  • .status()
  • .isLoading()
  • .error()

And functions.

  • .hasValue()
  • .reload()

It’s also a WritableResource. This means you can overwrite the value of the resource locally.

  • .set()
  • .update()
  • .asReadonly()

New to HttpResourceRef are the following signals and methods.

  • .headers()
  • .progress()
  • .statusCode()
  • .destroy()

Static URL

userProfiles = httpResource("/api/user-profiles");

Dynamic URL

// input, computed, signal, model, ...
selectedUserId: Signal<string> = ...;

// like "computed"
userProfile = httpResource(() => `/api/user-profiles/${this.selectedUserId()}`);

Full Control

Return a HttpResourceRequest with different (some optional) properties. You will know them from the HttpClient.

userProfile = httpResource(() => ({
  method: 'POST',
  url: `/api/user-profiles/${this.selectedUserId()}`,
  headers: ...,
  params: ...,
  body: ...,
  reportProgress: true,
  withCredentials: true,
  transferCache: ..., // Configures the server-side rendering transfer cache for this request.
}));

Remember: You shouldn’t use httpResource for sending updates/mutations to the backend. You can use this overload, if you need fine grained control over the request for fetching data and your backend is “weird”.

Options

As a second parameter you can provide a HttpResourceOptions object.

export interface HttpResourceOptions<TResult, TRaw> {
  defaultValue?: NoInfer<TResult>;
  equal?: ValueEqualityFn<NoInfer<TResult>>;
  injector?: Injector;
  map?: (value: TRaw) => TResult;
}

You may know what to do with the properties defaultValue, equal and injector.

Map the response

Per default the HttpResourceRef is typed with unknown. This is because “you never know what you get” from the “box of chocolates” backend (ask Forrest Gump). Luckily there are libraries which can parse unknown JSON data and provide a typesafe object. Explore valibot or zod.

These libraries provide some “parse” function, which returns a typesafe object. And the map option is the right place to plug in those function. With this in place, the TypeScript compiler will make sure the types are all right. And you can be sure, that you get what you expect.

My backend doesn’t deliver JSON!

The Angular team has you covered!

userProfile = httpResource.text(...);
userProfile = httpResource.blob(...);
userProfile = httpResource.arraybuffer(...);

This will not try to parse the content of your response as JSON, but will provide you the plain text, a blob or arraybuffer. That should cover everything you may need. Especially with the map function in place.

Can’t wait for the release! 🥰