# Angular 20 in 2026: Resource API, httpResource and Interview Questions
> Angular 20 introduces httpResource and stabilizes the Resource API for signal-based data fetching. A hands-on tutorial covering resource(), rxResource(), httpResource(), Zod validation, and common interview questions.
- Published: 2026-05-30
- Updated: 2026-05-30
- Author: SharpSkill
- Tags: angular, angular-20, resource-api, httpresource, signals, typescript, tutorial
- Reading time: 10 min
---
Angular 20 brings the [Resource API](https://angular.dev/guide/signals/resource) and `httpResource` to the forefront of signal-based data fetching. These experimental APIs replace verbose `HttpClient` subscription patterns with reactive primitives that track loading, error, and resolved states automatically.
> **What changed in Angular 20**
>
> The Resource API renames `request` to `params` and `loader` to `stream` (for `rxResource`). Status values are now string literals (`'idle'`, `'loading'`, `'resolved'`, `'error'`, `'reloading'`, `'local'`) instead of numeric enums. `httpResource` builds on `HttpClient`, supporting interceptors and Zod validation out of the box.
## The Three Resource Variants in Angular 20
Angular 20 offers three ways to load asynchronous data as signals. Each targets a different use case, but all share the same reactive model: declare dependencies, define a loader, and consume the result through signals.
- **`resource()`** works with Promises. Ideal when using `fetch()` or any promise-based API.
- **`rxResource()`** works with Observables. The right choice when RxJS operators like `debounceTime`, `retry`, or `switchMap` are needed.
- **`httpResource()`** wraps Angular's `HttpClient` directly. Interceptors, testing utilities, and schema validation work without extra wiring.
The key difference between `httpResource` and the other two: `httpResource` uses `HttpClient` under the hood, which means existing [interceptors](https://angular.dev/guide/http/interceptors) continue to work. The original `resource()` API bypassed `HttpClient` entirely, which was a major pain point in Angular 19.
## Building a User Profile with resource()
The `resource()` function accepts a `params` computation and a `loader` function. When signals inside `params` change, the loader re-executes automatically.
```typescript
// user-profile.component.ts
import { Component, signal, resource } from '@angular/core';
interface User {
id: number;
name: string;
email: string;
}
@Component({
selector: 'app-user-profile',
template: `
@if (userResource.hasValue()) {
{{ userResource.value().name }}
{{ userResource.value().email }}
} @else if (userResource.isLoading()) {
Loading profile...
} @else if (userResource.error()) {
Failed to load user
}
`,
})
export class UserProfileComponent {
userId = signal(1);
// params produces the reactive dependency
// loader receives it and returns a Promise
userResource = resource({
params: () => this.userId(),
loader: async ({ params: id, abortSignal }) => {
const res = await fetch(`/api/users/${id}`, { signal: abortSignal });
return res.json();
},
});
loadUser(id: number) {
this.userId.set(id); // triggers automatic refetch
}
}
```
The `abortSignal` parameter allows Angular to cancel in-flight requests when `userId` changes before the previous request completes. This prevents race conditions without manual subscription management.
## Reactive Data Fetching with httpResource
`httpResource` eliminates boilerplate by combining URL declaration and HTTP execution in a single call. It returns an `HttpResourceRef` that exposes `value`, `isLoading`, `error`, `status`, and `headers` as signals.
```typescript
// product-list.component.ts
import { Component, signal, computed } from '@angular/core';
import { httpResource } from '@angular/common/http';
interface Product {
id: number;
name: string;
price: number;
category: string;
}
@Component({
selector: 'app-product-list',
template: `
@if (products.hasValue()) {
@for (product of products.value(); track product.id) {
{{ product.name }}
{{ product.price | currency }}
}
} @else if (products.isLoading()) {
Loading products...
}
`,
})
export class ProductListComponent {
category = signal('electronics');
// httpResource re-fetches whenever category() changes
products = httpResource(() => ({
url: '/api/products',
params: { category: this.category() },
}));
filterByCategory(cat: string) {
this.category.set(cat); // pending request is cancelled, new one starts
}
}
```
Several details matter here. The function passed to `httpResource` returns a request configuration object. Angular tracks signal reads inside this function, so changing `category` triggers a new GET request. If a request is already in flight, Angular cancels it before starting the new one.
> **httpResource is for reads only**
>
> `httpResource` is designed for data fetching (GET requests). Using it for POST, PUT, or DELETE operations is unsafe because request cancellation could abort mutations mid-flight. For write operations, use `HttpClient` directly or wrap mutations in a service method.
## Schema Validation with Zod and httpResource
API responses from external services can drift from expected shapes. The `parse` option on `httpResource` integrates schema validation libraries like [Zod](https://zod.dev/) to catch mismatches at runtime instead of silently propagating corrupt data.
```typescript
// order.component.ts
import { Component, signal } from '@angular/core';
import { httpResource } from '@angular/common/http';
import { z } from 'zod';
// Define the expected shape with Zod
const OrderSchema = z.object({
id: z.number(),
status: z.enum(['pending', 'shipped', 'delivered', 'cancelled']),
total: z.number().positive(),
items: z.array(z.object({
productId: z.number(),
quantity: z.number().int().positive(),
unitPrice: z.number().positive(),
})),
createdAt: z.string().datetime(),
});
type Order = z.infer;
@Component({
selector: 'app-order',
template: `
@if (order.hasValue()) {
Order #{{ order.value().id }}
Status: {{ order.value().status }}
Total: {{ order.value().total | currency }}
} @else if (order.error()) {
Invalid order data received
}
`,
})
export class OrderComponent {
orderId = signal(42);
// parse validates the response before exposing it as a signal
order = httpResource(
() => `/api/orders/${this.orderId()}`,
{ parse: OrderSchema.parse }
);
}
```
When the API returns data that doesn't match `OrderSchema`, the resource transitions to the `'error'` status. The `parse` function's return type also determines the TypeScript type of `value()`, so schema definitions serve double duty as runtime validators and type generators.
## rxResource with Stream and Params in Angular 20
Angular 20 renames `loader` to `stream` and `request` to `params` in `rxResource`. This aligns the naming with the streaming semantics that `rxResource` supports. The `stream` function receives an Observable context and must return an Observable.
```typescript
// search.component.ts
import { Component, signal } from '@angular/core';
import { rxResource } from '@angular/core/rxjs-interop';
import { HttpClient } from '@angular/common/http';
import { inject } from '@angular/core';
import { debounceTime, switchMap } from 'rxjs';
interface SearchResult {
id: number;
title: string;
excerpt: string;
}
@Component({
selector: 'app-search',
template: `
@if (results.isLoading()) {
}
}
`,
})
export class SearchComponent {
private http = inject(HttpClient);
query = signal('');
// params (was "request") provides the reactive input
// stream (was "loader") returns an Observable
results = rxResource({
params: () => this.query(),
stream: ({ params: q }) =>
this.http.get('/api/search', {
params: { q },
}),
});
}
```
Unlike `httpResource`, `rxResource` gives full control over the Observable pipeline. Operators like `debounceTime` or `retry` can be chained inside `stream`. However, `httpResource` handles the most common case (single GET request) with less code.
## Status Tracking: String Literals Replace Enums
Angular 20 changes `ResourceStatus` from a numeric enum to a string union type. The six possible values provide granular insight into the resource lifecycle:
| Status | Meaning |
|---|---|
| `'idle'` | `params` returned `undefined` — no request issued |
| `'loading'` | First request in progress |
| `'reloading'` | Subsequent request after a previous success |
| `'resolved'` | Data available in `value()` |
| `'error'` | Request failed — `error()` contains the error |
| `'local'` | Value set locally via `.set()` or `.update()` |
```typescript
// status-demo.component.ts
import { Component, signal, resource } from '@angular/core';
@Component({
selector: 'app-status-demo',
template: `
}
}
`,
})
export class StatusDemoComponent {
filter = signal(undefined);
data = resource({
params: () => this.filter(),
loader: async ({ params: f, abortSignal }) => {
const res = await fetch(`/api/data?filter=${f}`, { signal: abortSignal });
return res.json();
},
});
}
```
Returning `undefined` from `params` sets the status to `'idle'` and prevents the loader from executing. This pattern works well for conditional data fetching — display a prompt until the user provides input, then load data.
> **Accessing value() in error state**
>
> Since Angular 20, calling `value()` on a resource in `'error'` status throws a runtime exception. Always guard reads with `hasValue()` or check `status()` before accessing `value()`. This is a breaking change from Angular 19 where `value()` returned `undefined` on error.
## Angular 20 httpResource Interview Questions
The Resource API is becoming a standard topic in [Angular interview questions](/blog/angular/angular-19-interview-questions-signals-ssr-must-know). Here are questions that test real understanding beyond surface-level familiarity with the API.
**Q: What problem does httpResource solve that resource() does not?**
`resource()` uses `fetch()` or any promise-based loader, bypassing Angular's `HttpClient`. This means interceptors (for auth tokens, logging, error handling) do not apply. `httpResource` uses `HttpClient` internally, so interceptors, testing utilities (`HttpTestingController`), and features like `withFetch()` work without additional configuration.
**Q: When should rxResource be preferred over httpResource?**
`rxResource` provides full Observable control through its `stream` function. Choose it when the data pipeline requires RxJS operators — debouncing search input, retrying failed requests with exponential backoff, or combining multiple streams with `combineLatest`. For straightforward GET requests, `httpResource` requires less code.
**Q: How does Angular handle concurrent requests when a signal changes rapidly?**
All three resource variants cancel pending requests when `params` produces a new value. For `httpResource` and `resource()`, the `AbortSignal` cancels the underlying fetch. For `rxResource`, Angular unsubscribes from the previous Observable. This prevents stale responses from overwriting fresh data.
**Q: What is the purpose of the `'local'` status?**
Calling `.set()` or `.update()` on a resource changes its value locally without triggering the loader. The status transitions to `'local'`, indicating the current value did not come from the server. This supports optimistic UI updates — the UI reflects the change immediately while a separate mutation request runs.
**Q: How does Zod integration work with httpResource?**
The `parse` option accepts any function with the signature `(data: unknown) => T`. When the HTTP response arrives, `httpResource` passes the parsed JSON through `parse` before setting `value()`. If `parse` throws (e.g., `ZodError`), the resource transitions to `'error'` status. The return type of `parse` determines the TypeScript type of `value()`, providing both runtime safety and compile-time types from a single schema definition.
For a deeper dive into [Angular signals](/technologies/angular/interview-questions/angular-signals) and how they integrate with the broader framework, the signals module covers computed signals, effects, and the reactivity model.
## Migrating from HttpClient Subscriptions to httpResource
Existing Angular applications typically fetch data with `HttpClient` subscriptions inside `ngOnInit` or use `AsyncPipe` with Observables. Migrating to `httpResource` follows a predictable pattern:
```typescript
// BEFORE: manual subscription in ngOnInit
@Component({ /* ... */ })
export class BeforeComponent implements OnInit, OnDestroy {
private http = inject(HttpClient);
private destroy$ = new Subject();
users: User[] = [];
loading = false;
error: string | null = null;
ngOnInit() {
this.loading = true;
this.http.get('/api/users')
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (data) => { this.users = data; this.loading = false; },
error: (err) => { this.error = err.message; this.loading = false; },
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
// AFTER: httpResource handles lifecycle automatically
@Component({ /* ... */ })
export class AfterComponent {
users = httpResource(() => '/api/users');
// No ngOnInit, no Subject, no manual unsubscribe
// Template uses users.value(), users.isLoading(), users.error()
}
```
The migration eliminates lifecycle management boilerplate. Subscription cleanup happens automatically when the component is destroyed. Loading and error states are built into the resource — no separate boolean flags needed.
For applications already using [standalone components](/blog/angular/angular-standalone-components-migration-best-practices), the migration is straightforward: replace the `HttpClient` injection and subscription logic with a single `httpResource` declaration.
## Conclusion
- `httpResource` replaces manual `HttpClient` subscriptions with a single reactive declaration that handles loading, error, and cancellation automatically
- Use `resource()` for promise-based APIs, `rxResource()` for Observable pipelines with RxJS operators, and `httpResource()` for standard HTTP calls with interceptor support
- Angular 20 renames `request` to `params` and `loader` to `stream` in `rxResource` — update existing code accordingly
- Status values are now string literals (`'idle'`, `'loading'`, `'resolved'`, `'error'`, `'reloading'`, `'local'`), replacing numeric enums from Angular 19
- The `parse` option on `httpResource` integrates Zod or Valibot for runtime schema validation that also drives TypeScript types
- Calling `value()` on a resource in error state throws in Angular 20 — always guard with `hasValue()` or check `status()` first
- All resource variants cancel in-flight requests automatically when dependencies change, preventing race conditions without manual cancellation logic
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/en/blog/angular/angular-20-resource-api-httpresource-tutorial