DEV Community

Giorgio Galassi
Giorgio Galassi

Posted on β€’ Originally published at Medium

Angular v19+ β€” Understanding the New httpResource() API πŸ”₯πŸš€

Angular v19.2 is here, bringing yet another set of features designed to simplify our development experience.

This time, the spotlight is on the new httpResource() API, which joins the resource family, alongside resource() and rxResource().

We previously explored these APIs in another article that you can find below:

❓How Does It Work?

Let’s first take a look at how the rxResource() API is typically used to perform a backend call:

import { rxResource } from "@angular/core/rxjs-interop";

rxUsers = rxResource<User[], string | undefined>({
  request: () => this.query(),
  loader: ({ request }) =>
    this.#http.get<{ users: User[] }>(`${API_URL}/search?q=${request}`)
    .pipe(
      distinctUntilChanged(),
      map(({ users }) => users),
      catchError(() => {
        throw Error('Unable to load!');
      })
    ),
});
Enter fullscreen mode Exit fullscreen mode

In this example, we still rely on HttpClient, and the .get() method returns an Observable, which we process using RxJS operators like map() and catchError().

βœ… Nothing is wrong with this approach, but what if we wanted a simpler API that integrates directly with HttpClient and eliminates the need to manually handle Observables?

πŸš€ Exploring httpResource()

Since httpResource() is built directly on top of HttpClient, we no longer need to manually handle API requests.

Instead, we can simply pass the API URL as a parameter:

import { httpResource } from "@angular/common/http";

httpUsers = httpResource<User[]>(API_URL);
Enter fullscreen mode Exit fullscreen mode

With just this single line of code, httpResource() automatically manages the request logic, leveraging Angular's HttpClient behind the scenes.

βœ… But what if we need more flexibility?
Real-world applications often require:

  • Setting default values to prevent UI issues.
  • Parsing API responses before assigning them to the Signal.

Let’s explore how httpResource() handles this next.

πŸ“Œ Using defaultValue and parse for Safer Data Handling

By default, httpResource() expects valid data from the API. However, in real-world scenarios, we might need to:

βœ… Ensure a default value when the API returns null or undefined.
βœ… Validate and parse responses before assigning them to the signal.

Using Zod for schema validation, we can enforce type safety directly within httpResource():

import { httpResource } from "@angular/common/http";
import { z } from 'zod';

// Define a schema using Zod
const UserSchema = z.object({ name: z.string(), surname: z.string() });
const UserResultsSchema = z.array(UserSchema);

type User = z.infer(typeof UserSchema);

httpUsers = httpResource<User[]>(
  () => `${API_URL}/search?q=${this.query()}`,
  {
    defaultValue: [], // Ensures an empty array if no data is returned
    parse: (data) => UserResultsSchema.parse(data) // Validates and parses the API response
  }
);
Enter fullscreen mode Exit fullscreen mode

βœ… Why is this useful?

  • Prevents UI errors from unexpected null values.
  • Ensures type safety by validating API responses.
  • Eliminates extra validation logic in the component

πŸ” Advanced Use Case

Passing just the API URL is often not enough. In our rxResource() example, we included a search query parameter tied to the query() signal. Let’s explore how to achieve the same behavior using the httpResource() API:

import { httpResource } from "@angular/common/http";

httpUsers = httpResource<User[]>(() => `${API_URL}/search?q=${this.query()}`);
Enter fullscreen mode Exit fullscreen mode

By passing a reactive function to httpResource(), the API automatically listens for changes in the query signal.
Every time query() updates, a new request is triggered, ensuring that the latest data is always fetched.


⚠️ Important Considerations

Each new API request cancels the previous one, similar to how the switchMap operator works in RxJS.
This is the default behavior of httpResource().

Be cautious when using this API for updates (PUT, POST, DELETE requests).
A subsequent update could cancel a previous request before it completes, leading to potential data loss.

❌ Avoid using httpResource() for mutations
βœ”οΈ Use it for fetching data (GET requests) instead


⏳ Enabling API Calls Only When Needed

By default, httpResource() automatically executes the API call whenever the request function changes. However, in some cases, we don't want the API to trigger immediatelyβ€”for example, when data fetching should only happen after a user action (e.g., clicking a button).

We can conditionally enable the request by returning undefined when it should not be executed:

import { httpResource } from "@angular/common/http";

httpUsers = httpResource<User[]>(() => {
  const enabled = this.isApiEnabled();
  return enabled ? `${API_URL}/search?q=${this.query()}` : undefined;
});
Enter fullscreen mode Exit fullscreen mode

βœ… Why is this useful?

  • Avoids unnecessary API calls when data isn’t required.
  • Helps control execution flow, ensuring the request happens only when a condition is met.
  • Ideal for scenarios like lazy loading, conditional search, or toggle-based requests.

⚑ What If I Want to Use Pipeable Operators?

Everything is working as expected, but there’s a key difference between rxResource() and httpResource().

Since httpResource() does not expose an observable, we can't apply RxJS operators like distinctUntilChanged() or debounceTime() directly.
This means we need an alternative approach to manage request optimizations.

There are two possible solutions:

  1. Use rxResource() when you need to transform or process the response using RxJS operators.
  2. Create a utility function that allows us to bridge the gap, applying RxJS operators to Signals before passing them to httpResource().

Let’s explore Approach #2 in detail.

πŸ”„ From Signal to Observable and Back to Signal

Angular provides interoperability functions that help bridge the gap between Signals and Observables:

  • toObservable() β†’ Converts a Signal into an Observable.
  • toSignal() β†’ Converts an Observable back into a Signal.

By leveraging these utilities, we can apply RxJS operators like debounceTime() and distinctUntilChanged() while keeping httpResource() reactive.

πŸ”— Applying RxJS Operators to Signals

import { signal } from "@angular/core";
import { httpResource } from "@angular/common/http";
import { toObservable, toSignal } from "@angular/core/rxjs-interop";

import { debounceTime, distinctUntilChanged } from 'rxjs/operators';

inputQuery = signal<string>('');

inputQuery$ = toObservable(this.inputQuery).pipe(
  distinctUntilChanged(), 
  debounceTime(400)
);

query = toSignal(this.inputQuery$);

httpUsers = httpResource<User[]>(() => `${API_URL}/search?q=${this.query()}`);
Enter fullscreen mode Exit fullscreen mode

Here, we use a Signal to manage the input event and trigger API requests while leveraging an Observable to handle the stream flow efficiently.

Note:
This example works as expected, but since we are delaying the event and not the actual API request, the isLoading state could not reflect the truth.

πŸ›  Simplifying with a Utility Function

To avoid writing this logic repeatedly, we can create a utility function that applies debouncing and distinct filtering to any Signal:

import { Signal } from "@angular/core";
import { toObservable, toSignal } from "@angular/core/rxjs-interop";
import { distinctUntilChanged, debounceTime } from "rxjs";

export function debounceDistinctSignal<T>(signal: Signal<T>, time: number) {
  const obs$ = toObservable(signal).pipe(
    distinctUntilChanged(),
    debounceTime(time)
  );

  return toSignal(obs$);
}
Enter fullscreen mode Exit fullscreen mode

Now, we can simplify our original implementation like this:

import { httpResource } from "@angular/common/http";
import { debounceDistinctSignal } from 'signal-utils';

query = debounceDistinctSignal(this.inputQuery, 400);

httpUsers = httpResource<User[]>(() => `${API_URL}/search?q=${this.query()}`);
Enter fullscreen mode Exit fullscreen mode

🎯 Try it in Action! πŸš€

Want to see httpResource() in action? Check out this interactive StackBlitz demo, where you can:

βœ… Experiment with httpResource() for API calls
βœ… Test a search bar with debounce & distinctUntilChanged
βœ… Compare side-by-side with rxResource() and resource()

πŸ“– Summary & Next Steps

Here’s what we’ve learned in this article:

httpResource() simplifies API calls by integrating directly with HttpClient.
toObservable() and toSignal() provide interoperability between Signals and RxJS.
A custom utility function (debounceDistinctSignal()) helps clean up our code and makes this approach reusable.


Thank you for staying with me, and I hope everything was clear. Feel free to explore more of my articles and follow me on LinkedIn!

See you in the next one!

Best, G.

Image of Timescale

πŸ“Š Benchmarking Databases for Real-Time Analytics Applications

Benchmarking Timescale, Clickhouse, Postgres, MySQL, MongoDB, and DuckDB for real-time analytics. Introducing RTABench πŸš€

Read full post β†’

Top comments (0)

5 Playwright CLI Flags That Will Transform Your Testing Workflow

  • 0:56 --last-failed
  • 2:34 --only-changed
  • 4:27 --repeat-each
  • 5:15 --forbid-only
  • 5:51 --ui --headed --workers 1

Learn how these powerful command-line options can save you time, strengthen your test suite, and streamline your Playwright testing experience. Click on any timestamp above to jump directly to that section in the tutorial!

πŸ‘‹ Kindness is contagious

If this post resonated with you, feel free to hit ❀️ or leave a quick comment to share your thoughts!

Okay