In Angular, what does each resource status value mean, and what does value() return in each state, including after the loader fails?
answer
- six string statuses
- loading versus reloading
- which states keep the old value
- error state and value()
- hasValue() as the guard
basics
~20 sA resource reports idle, loading, reloading, resolved, error or local. value() is undefined (or defaultValue) when idle or loading, keeps the old data while reloading, and throws in the error state, so guard reads with hasValue().
solid answer
~40 s`status()` returns one of six strings. `'idle'` means no request (params were `undefined`); `'loading'` means params changed and the loader runs, with `value()` reset to `undefined` or the `defaultValue`; `'reloading'` follows `reload()` and keeps the previous value visible; `'resolved'` holds the loader's result; `'local'` means you wrote the value with `set()` or `update()`; `'error'` means the loader failed. In the error state `value()` **throws** — even with a `defaultValue` — so templates guard with `hasValue()`, which is `false` in error and when the value is `undefined`, and also narrows the type. `isLoading()` is `true` for both loading and reloading, and `error()` returns the `Error`. Throwing on an errored read was introduced in Angular 20.
code
ts · 26 linesimport { Injectable, computed, resource, signal } from '@angular/core';
interface Order {
id: string;
total: number;
}
@Injectable({ providedIn: 'root' })
export class OrderHistoryStore {
readonly userId = signal<string | undefined>(undefined);
readonly orders = resource({
params: () => this.userId(),
loader: ({ params: id, abortSignal }) =>
fetch(`/api/users/${id}/orders`, { signal: abortSignal })
.then((r) => r.json() as Promise<Order[]>),
});
// Safe: hasValue() guards the read and narrows the type.
readonly orderCount = computed(() =>
this.orders.hasValue() ? this.orders.value().length : 0,
);
// Unsafe: throws while orders.status() === 'error'.
readonly unsafeCount = computed(() => this.orders.value()?.length ?? 0);
}go deeper
Recall the six status strings and that value() is empty while loading. Know to check hasValue() before showing the data.
Explain loading versus reloading, what value() returns in each state, and why value() throws in error even with a defaultValue.
Show a template branch order that keeps data visible on refresh, never reads value() in error, and routes failures deliberately.
Decide how the team renders async states consistently, and whether the raw status signals or a shared wrapper component should be the convention.
## Why the status matters A `Resource` in Angular exposes its async state through several signals instead of one promise. Reading them correctly is what separates a component that shows the right thing in every moment from one that flashes stale orders, blanks out on refresh or crashes the template on the first failed request. The central signal is `status()`, which returns a `ResourceStatus` string. This answer assumes Angular 22.2. ## The six statuses | `status()` | What happened | `value()` returns | | --- | --- | --- | | `'idle'` | No valid request: `params` returned `undefined`, or the resource was destroyed | `undefined`, or `defaultValue` | | `'loading'` | `params` produced a new value and the loader is running | `undefined`, or `defaultValue` | | `'reloading'` | `reload()` was called for the same params | The previously loaded value | | `'resolved'` | The loader finished successfully | The loader's result | | `'error'` | The loader rejected or threw | **Throws** an error | | `'local'` | Code wrote the value with `set()` or `update()` | The locally written value | Two rows deserve a closer look. - **Loading versus reloading.** A change of `params` means "different data", so the old value would be wrong for the new request and is dropped. `reload()` means "same data, fresher", so the old value stays on screen while the loader runs. Selecting a different customer gives `'loading'`; a refresh button on the same customer gives `'reloading'`. - **Error.** Reading `value()` in the error state throws an error whose message says the resource is in an error state and whose `cause` is the loader's error. A `defaultValue` does **not** change this. In a template, that throw reaches Angular's `ErrorHandler` like any other exception during rendering. ## The helper signals - `isLoading()` — `true` when the status is `'loading'` or `'reloading'`. - `error()` — the most recent `Error`, or `undefined`. If a loader throws something that is not an `Error`, Angular wraps it in one and keeps the original as `cause`. - `hasValue()` — `true` only when the resource is not in error and `value()` is not `undefined`. It is reactive, and it is a **type guard**: inside an `if (orders.hasValue())` branch the type of `value()` loses `undefined`. - `snapshot` — a signal of `{status, value}` or `{status: 'error', error}` for code that wants the whole state as one object. ## A template that handles every state ```html @if (orders.hasValue()) { @if (orders.isLoading()) { <p>Refreshing...</p> } <app-order-table [orders]="orders.value()" /> } @else if (orders.error(); as err) { <p>Could not load orders: {{ err.message }}</p> } @else if (orders.isLoading()) { <p>Loading orders...</p> } @else { <p>Select a customer to see their orders.</p> } ``` The order of the branches matters: 1. Check `hasValue()` first, so a reload keeps the table visible and only adds a small indicator. 2. Check `error()` before any read of `value()`, because that read would throw. 3. Fall through to the loading and idle messages. ## Two edge cases worth knowing - **Reload after an error.** Calling `reload()` from the error state moves the resource to `'reloading'`, and while it runs `value()` returns the default rather than throwing, so a retry button does not crash the view. - **`defaultValue` changes the type, not the error rule.** With `defaultValue: []`, `value()` is typed `Order[]` and returns `[]` while idle or loading — convenient for a list — but it still throws in the error state. ## History Before Angular 20, reading `value()` in the error state returned `undefined`; Angular 20 changed it to throw, and some older articles still show the old behaviour. The official status table in the guide also lists `undefined` for the error row, while the `Resource` interface documents the throw — the implementation throws. `resource()` itself was experimental from Angular 19 and became stable public API in Angular 22.
- Why does a params change reset value() while reload() keeps it?A params change means different data, so the old orders would belong to the wrong customer and are dropped. `reload()` asks for the same data again, so the old value stays visible while the loader runs and the status reads `'reloading'` instead of `'loading'`.
- Does passing defaultValue make value() safe to read in every state?No. `defaultValue` fills `value()` while idle or loading and removes `undefined` from its type, but in the `'error'` state `value()` still throws. Check `hasValue()`, `error()` or `status()` before reading it.
- What does isLoading() report during a reload?`true`. `isLoading()` is derived as status `'loading'` or `'reloading'`, so a refresh shows as loading even though `value()` still holds the previous data. Use `hasValue()` alongside it to keep the data on screen with a small refresh indicator.
A resource is like a departures board: 'loading' blanks the gate when your flight changes, 'reloading' keeps showing the old gate while it double-checks, and 'error' is a sign that says ask at the desk rather than guessing a gate.
saying these in an interview costs you the question
- value() returns undefined when the resource is in the error state.
- A defaultValue stops value() from throwing after a failed load.
- Loading and reloading are two names for the same state.
- value() keeps the previous user's data while new params load.
- isLoading() is false during a reload because a value already exists.
- An optional chain like value()?.length guards the error state.