# 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()) {

Searching...

} @if (results.hasValue()) { @for (item of results.value(); track item.id) {
{{ item.title }}
} } `, }) 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: `

Status: {{ data.status() }}

@switch (data.status()) { @case ('loading') { } @case ('reloading') { } @case ('resolved') { } @case ('error') { } @case ('idle') {

Select a filter to load data

} } `, }) 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