skip to content

Signal-Driven Fetching

httpResource() refetches through HttpClient whenever its reactive request function changes and exposes value, status, error and headers as signals. Interviewers ask when toSignal or rxResource wins.

part ofAngularoverview, primer and where to startread it →
on this pageshow

explore

questions

4

In Angular, when does httpResource() send a request for a reviews panel keyed by a productId signal, and what does it expose?

level: juniorimportance: must knowfreq 55%

answer

  1. a request function, not a call
  2. signals read inside are tracked
  3. undefined means stay idle
  4. state as signals, not a stream

basics

~20 s

httpResource() tracks the signals its request function reads and sends the request eagerly, then again whenever one of them changes, cancelling any pending request. Returning undefined keeps it idle. It exposes value, status, error, isLoading, headers and statusCode as signals.

solid answer

~40 s

`httpResource()` from `@angular/common/http` takes a **reactive request function** — returning a URL string or an `HttpResourceRequest` object — and turns it into a resource whose state is signals. The function's signal reads are tracked, so `() => `/api/products/${productId()}/reviews`` fetches right after the resource is created and again whenever `productId` changes; a pending request is cancelled before the new one starts. Unlike `HttpClient`, whose Observables do nothing until subscribed, `httpResource` starts **eagerly**. Returning `undefined` means "no request yet" and leaves the resource `idle`. You read `value()`, `status()`, `error()`, `isLoading()` and `hasValue()`, plus HTTP extras `headers()`, `statusCode()` and `progress()`, and can call `reload()`. It runs through `HttpClient`, so interceptors apply, and it must be created in an injection context.

code

ts · 40 lines
ts
import { Component, input } from '@angular/core';
import { httpResource } from '@angular/common/http';

interface Review {
  id: string;
  rating: number;
  text: string;
}

@Component({
  selector: 'app-product-reviews',
  template: `
    @if (reviews.error()) {
      <p>Reviews are unavailable right now.</p>
    } @else if (reviews.isLoading()) {
      <p>Loading reviews...</p>
    } @else {
      <ul>
        @for (review of reviews.value(); track review.id) {
          <li>{{ review.rating }}/5 - {{ review.text }}</li>
        } @empty {
          <li>No reviews yet.</li>
        }
      </ul>
    }
  `,
})
export class ProductReviews {
  readonly productId = input<string | undefined>();

  // Tracked: every productId change sends a new GET and cancels a pending one.
  // Returning undefined keeps the resource idle (no request at all).
  readonly reviews = httpResource<Review[]>(
    () => {
      const id = this.productId();
      return id ? `/api/products/${id}/reviews` : undefined;
    },
    { defaultValue: [] },
  );
}

go deeper

for a junior

Know that httpResource takes a function, refetches when signals read in it change, and exposes value, isLoading and error for the template.

for a middle

Explain eager versus cold behaviour, cancellation of the pending request, the undefined-means-idle idiom and the injection-context requirement.

for a senior

Place httpResource in a data layer deliberately: reads keyed by signals, interceptors still applying, and writes kept on HttpClient.

for a principal

Decide how far a codebase moves from Observable-returning services to signal resources, and how mixed styles stay consistent for error handling and testing.

## What `httpResource` is `httpResource` is a function in `@angular/common/http` that wraps `HttpClient` in Angular's **resource** model: an asynchronous value whose state is exposed through **signals**. The Angular guide describes it as a reactive wrapper around `HttpClient` that gives you the request status and response as signals. It is public API in Angular 22 (it appeared as experimental in 19.2). You give it a **request function** instead of a URL: ```ts readonly reviews = httpResource<Review[]>(() => `/api/products/${this.productId()}/reviews`); ``` The function may return: - a **URL string**, for a plain `GET`; - an **`HttpResourceRequest`** object with `url`, `method`, `params`, `headers`, `body`, `context`, `timeout`, `transferCache` and fetch options such as `cache` or `priority`; - **`undefined`**, meaning there is nothing to fetch yet. ## When a request is sent 1. **Right after creation.** `httpResource` is **eager**: an internal effect computes the request and sends it without anyone subscribing or reading `value()`. That is the opposite of `HttpClient`, whose cold Observables send nothing until subscribed. 2. **When a tracked signal changes.** Every signal read inside the request function becomes a dependency. When `productId` changes, the request is recomputed and a new `GET` goes out. 3. **On `reload()`.** An explicit refetch with the same request. Three rules come with that: - If a request is **still pending** when the inputs change, the resource **cancels** it before issuing the new one, so an old product's reviews cannot overwrite the new product's. - If the function returns **`undefined`**, no request is made and the status is `idle` — the idiom for "wait until the id is known". - Only signals read **inside** the function are tracked. A plain field read there is captured once and never triggers a refetch. ## What it exposes | Member | Type | Meaning | |---|---|---| | `value()` | `T`, or `T` or `undefined` without a `defaultValue` | The parsed response body; the `defaultValue` option, or `undefined`, while idle or loading | | `status()` | One of six strings: `idle`, `loading`, `reloading`, `resolved`, `error`, `local` | Where the resource is in its lifecycle | | `error()` | `Error` or `undefined` | The failure, e.g. an `HttpErrorResponse` | | `isLoading()` | `boolean` | `true` while `loading` or `reloading` | | `hasValue()` | `boolean` | Whether a value is available; `false` in the error state | | `headers()` | `HttpHeaders` or `undefined` | Response headers once resolved or errored | | `statusCode()` | `number` or `undefined` | The HTTP status, also on error | | `progress()` | `HttpProgressEvent` or `undefined` | Download progress when `reportProgress` is set | | `reload()`, `set()`, `update()`, `destroy()` | methods | Refetch, override the value locally, stop the resource | The body is parsed as **JSON** by default; `httpResource.text()`, `httpResource.blob()` and `httpResource.arrayBuffer()` read it differently. ## Where it can be created `httpResource` needs an **injection context**: a component or service field initializer or constructor. Elsewhere, pass an `injector` option. The resource lives as long as that injector, and destroying the component destroys the resource and cancels its request. Because it is built on `HttpClient`, it goes through the same interceptors (auth headers, error mapping) and can be tested with the same HTTP testing tools. ## What it is not for The guide advises against `httpResource` for **mutations** such as `POST` or `PUT`: an eager, re-running request suits reading data, not writing it. Use `HttpClient` directly for writes. ## Common mistakes - Passing a string instead of a function: `httpResource('/api/...')` does not type-check, because the request must be a function so it can be tracked. - Reading `this.productId` (a plain property) instead of a signal, then wondering why the resource never refetches. - Creating the resource inside a click handler, outside any injection context. - Expecting it to wait for a subscriber, as `HttpClient` does.

  • Why does an httpResource request function that returns undefined not send a request?
    `undefined` is the resource model's way of saying there is no valid request yet, so the resource moves to `idle` and makes no call; `value()` is the `defaultValue` or `undefined`. It is the idiom for dependent data: return `undefined` until the `productId` signal has a value, and the first real id then triggers the request automatically.
  • What happens to a pending httpResource request when productId changes before its response arrives?
    The resource recomputes the request and cancels the pending one before sending the new request; under the hood it unsubscribes from the `HttpClient` Observable, which aborts the call. The status goes to `loading` for the new product, so a late response for the old product never becomes the value.

saying these in an interview costs you the question

  • httpResource waits for a template or subscriber before sending its request.
  • httpResource takes a URL string, like HttpClient.get().
  • Changing productId while a request is pending lets both responses race to set the value.
  • httpResource bypasses HttpClient, so interceptors do not run for it.
  • httpResource is the recommended way to send POST and PUT requests in Angular 22.
open as a page

In Angular, what does httpResource's parse option do that httpResource<Review[]>() alone does not, and how does it work with httpResource.text() or blob()?

level: middleimportance: should knowfreq 36%

basics

~20 s

httpResource<Review[]>() only asserts a type. parse receives the raw body (unknown for JSON, string for text(), Blob for blob()), validates or transforms it, and its return type becomes the value type; a throw puts the resource in error.

open as a page

In Angular, how should a template read an httpResource's value, error and statusCode so a 404 for an unknown product does not crash it?

level: middleimportance: should knowfreq 45%

basics

~20 s

Check error() or hasValue() before reading value(), because value() throws while the resource is in the error state. For a 404, error() holds the HttpErrorResponse and statusCode() returns 404, so the template can show a not-found message.

open as a page

In an Angular 22 app, when would you fetch the reviews panel's data with httpResource, and when with HttpClient plus toSignal() or rxResource() instead?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Use httpResource for signal-keyed GET reads shown in a view. Use HttpClient directly for writes, rxResource when the loader must be an Observable pipeline or an existing service method, and toSignal for one fixed stream that does not refetch when inputs change.

open as a page