In Angular, when does httpResource() send a request for a reviews panel keyed by a productId signal, and what does it expose?
answer
- a request function, not a call
- signals read inside are tracked
- undefined means stay idle
- state as signals, not a stream
basics
~20 shttpResource() 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 linesimport { 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
Know that httpResource takes a function, refetches when signals read in it change, and exposes value, isLoading and error for the template.
Explain eager versus cold behaviour, cancellation of the pending request, the undefined-means-idle idiom and the injection-context requirement.
Place httpResource in a data layer deliberately: reads keyed by signals, interceptors still applying, and writes kept on HttpClient.
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.