# Angular 20 en 2026: Resource API, httpResource y preguntas de entrevista
> Angular 20 introduce httpResource y estabiliza la Resource API para la obtención de datos basada en signals. Un tutorial práctico sobre resource(), rxResource(), httpResource(), la validación con Zod y las preguntas de entrevista más comunes.
- Published: 2026-05-30
- Updated: 2026-06-06
- Author: SharpSkill
- Tags: angular, angular-20, resource-api, httpresource, signals, typescript, tutorial
- Reading time: 10 min
---
Angular 20 coloca la [Resource API](https://angular.dev/guide/signals/resource) y `httpResource` en el centro de la obtención de datos basada en signals. Estas API experimentales reemplazan los verbosos patrones de suscripción de `HttpClient` por primitivas reactivas que rastrean automáticamente los estados de carga, error y resolución.
> **Qué cambia en Angular 20**
>
> La Resource API renombra `request` a `params` y `loader` a `stream` (para `rxResource`). Los valores de estado ahora son literales de cadena (`'idle'`, `'loading'`, `'resolved'`, `'error'`, `'reloading'`, `'local'`) en lugar de enumeraciones numéricas. `httpResource` se construye sobre `HttpClient` y admite de forma nativa los interceptors y la validación con Zod.
## Las tres variantes de Resource en Angular 20
Angular 20 ofrece tres maneras de cargar datos asíncronos como signals. Cada una apunta a un caso de uso distinto, pero todas comparten el mismo modelo reactivo: declarar dependencias, definir un loader y consumir el resultado mediante signals.
- **`resource()`** funciona con Promises. Ideal al usar `fetch()` o cualquier API basada en promesas.
- **`rxResource()`** funciona con Observables. La opción correcta cuando se necesitan operadores RxJS como `debounceTime`, `retry` o `switchMap`.
- **`httpResource()`** envuelve directamente el `HttpClient` de Angular. Los interceptors, las utilidades de prueba y la validación de esquemas funcionan sin cableado adicional.
La diferencia clave entre `httpResource` y las otras dos: `httpResource` usa `HttpClient` por debajo, lo que significa que los [interceptors](https://angular.dev/guide/http/interceptors) existentes siguen funcionando. La API `resource()` original evitaba por completo `HttpClient`, un punto débil importante en Angular 19.
## Construir un perfil de usuario con resource()
La función `resource()` acepta una función de cálculo `params` y una función `loader`. Cuando los signals dentro de `params` cambian, el loader se vuelve a ejecutar automáticamente.
```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
}
}
```
El parámetro `abortSignal` permite que Angular cancele las solicitudes en curso cuando `userId` cambia antes de que la solicitud anterior termine. Esto evita condiciones de carrera sin gestión manual de las suscripciones.
## Obtención reactiva de datos con httpResource
`httpResource` elimina el código repetitivo al combinar la declaración de la URL y la ejecución HTTP en una sola llamada. Devuelve un `HttpResourceRef` que expone `value`, `isLoading`, `error`, `status` y `headers` como 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
}
}
```
Aquí importan varios detalles. La función pasada a `httpResource` devuelve un objeto de configuración de solicitud. Angular rastrea las lecturas de signals dentro de esa función, por lo que cambiar `category` dispara una nueva solicitud GET. Si ya hay una solicitud en curso, Angular la cancela antes de iniciar la nueva.
> **httpResource es solo para lecturas**
>
> `httpResource` está diseñado para la obtención de datos (solicitudes GET). Usarlo para operaciones POST, PUT o DELETE es inseguro, porque la cancelación de la solicitud podría abortar una mutación a mitad de camino. Para operaciones de escritura, usar `HttpClient` directamente o envolver las mutaciones en un método de servicio.
## Validación de esquemas con Zod y httpResource
Las respuestas de servicios externos pueden desviarse de la forma esperada. La opción `parse` de `httpResource` integra bibliotecas de validación de esquemas como [Zod](https://zod.dev/) para detectar discrepancias en tiempo de ejecución, en lugar de propagar silenciosamente datos corruptos.
```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 }
);
}
```
Cuando la API devuelve datos que no coinciden con `OrderSchema`, la resource pasa al estado `'error'`. El tipo de retorno de la función `parse` también determina el tipo TypeScript de `value()`, por lo que las definiciones de esquema cumplen una doble función: validadores en tiempo de ejecución y generadores de tipos.
## rxResource con stream y params en Angular 20
Angular 20 renombra `loader` a `stream` y `request` a `params` en `rxResource`. Este cambio alinea la nomenclatura con la semántica de flujo que `rxResource` admite. La función `stream` recibe un contexto de Observable y debe devolver un 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 },
}),
});
}
```
A diferencia de `httpResource`, `rxResource` ofrece control total sobre el pipeline de Observable. Operadores como `debounceTime` o `retry` pueden encadenarse dentro de `stream`. Sin embargo, `httpResource` resuelve el caso más común (una sola solicitud GET) con menos código.
## Seguimiento del estado: los literales de cadena reemplazan a las enumeraciones
Angular 20 transforma `ResourceStatus` de una enumeración numérica a un tipo de unión de cadenas. Los seis valores posibles ofrecen una visión detallada del ciclo de vida de la resource:
| Estado | Significado |
|---|---|
| `'idle'` | `params` devolvió `undefined` — no se emitió ninguna solicitud |
| `'loading'` | Primera solicitud en curso |
| `'reloading'` | Solicitud posterior tras un éxito previo |
| `'resolved'` | Datos disponibles en `value()` |
| `'error'` | La solicitud falló — `error()` contiene el error |
| `'local'` | Valor establecido localmente mediante `.set()` o `.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();
},
});
}
```
Devolver `undefined` desde `params` fija el estado en `'idle'` e impide que el loader se ejecute. Este patrón funciona bien para la obtención condicional de datos: mostrar un aviso hasta que el usuario proporcione una entrada y luego cargar los datos.
> **Acceder a value() en estado de error**
>
> Desde Angular 20, llamar a `value()` sobre una resource en estado `'error'` lanza una excepción en tiempo de ejecución. Siempre proteger las lecturas con `hasValue()` o comprobar `status()` antes de acceder a `value()`. Se trata de un cambio incompatible respecto a Angular 19, donde `value()` devolvía `undefined` en caso de error.
## Preguntas de entrevista sobre httpResource en Angular 20
La Resource API se está convirtiendo en un tema estándar de las [preguntas de entrevista de Angular](/blog/angular/angular-19-interview-questions-signals-ssr-must-know). Estas preguntas evalúan una comprensión real, más allá de una familiaridad superficial con la API.
**P: ¿Qué problema resuelve `httpResource` que `resource()` no resuelve?**
`resource()` usa `fetch()` o cualquier loader basado en promesas, evitando el `HttpClient` de Angular. Esto significa que los interceptors (para tokens de autenticación, registro, manejo de errores) no se aplican. `httpResource` usa `HttpClient` internamente, por lo que los interceptors, las utilidades de prueba (`HttpTestingController`) y características como `withFetch()` funcionan sin configuración adicional.
**P: ¿Cuándo conviene preferir `rxResource` sobre `httpResource`?**
`rxResource` ofrece control total del Observable a través de su función `stream`. Conviene elegirlo cuando el pipeline de datos requiere operadores RxJS: aplicar debounce a la entrada de búsqueda, reintentar solicitudes fallidas con backoff exponencial o combinar varios flujos con `combineLatest`. Para solicitudes GET sencillas, `httpResource` requiere menos código.
**P: ¿Cómo gestiona Angular las solicitudes concurrentes cuando un signal cambia con rapidez?**
Las tres variantes de resource cancelan las solicitudes en curso cuando `params` produce un nuevo valor. Para `httpResource` y `resource()`, el `AbortSignal` cancela el fetch subyacente. Para `rxResource`, Angular se desuscribe del Observable anterior. Esto evita que respuestas obsoletas sobrescriban datos frescos.
**P: ¿Cuál es el propósito del estado `'local'`?**
Llamar a `.set()` o `.update()` sobre una resource cambia su valor localmente sin disparar el loader. El estado pasa a `'local'`, lo que indica que el valor actual no proviene del servidor. Esto facilita las actualizaciones optimistas de la interfaz: la UI refleja el cambio de inmediato mientras se ejecuta una solicitud de mutación independiente.
**P: ¿Cómo funciona la integración de Zod con `httpResource`?**
La opción `parse` acepta cualquier función con la firma `(data: unknown) => T`. Cuando llega la respuesta HTTP, `httpResource` pasa el JSON analizado a través de `parse` antes de establecer `value()`. Si `parse` lanza una excepción (por ejemplo, `ZodError`), la resource pasa al estado `'error'`. El tipo de retorno de `parse` determina el tipo TypeScript de `value()`, lo que aporta seguridad en tiempo de ejecución y tipos en tiempo de compilación a partir de una única definición de esquema.
Para profundizar en los [signals de Angular](/technologies/angular/interview-questions/angular-signals) y su integración con el framework en general, el módulo de signals cubre los computed signals, los effects y el modelo de reactividad.
## Migrar de suscripciones HttpClient a httpResource
Las aplicaciones Angular existentes suelen obtener datos con suscripciones de `HttpClient` dentro de `ngOnInit`, o usan el `AsyncPipe` con Observables. La migración a `httpResource` sigue un patrón predecible:
```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()
}
```
La migración elimina el código repetitivo de gestión del ciclo de vida. La limpieza de las suscripciones ocurre automáticamente cuando se destruye el componente. Los estados de carga y error están integrados en la resource: no se necesitan banderas booleanas separadas.
Para las aplicaciones que ya usan [componentes standalone](/blog/angular/angular-standalone-components-migration-best-practices), la migración es directa: reemplazar la inyección de `HttpClient` y la lógica de suscripción por una única declaración `httpResource`.
## Conclusión
- `httpResource` reemplaza las suscripciones manuales a `HttpClient` por una sola declaración reactiva que gestiona automáticamente la carga, el error y la cancelación
- Usar `resource()` para API basadas en promesas, `rxResource()` para pipelines de Observable con operadores RxJS y `httpResource()` para llamadas HTTP estándar con soporte de interceptors
- Angular 20 renombra `request` a `params` y `loader` a `stream` en `rxResource` — actualizar el código existente en consecuencia
- Los valores de estado ahora son literales de cadena (`'idle'`, `'loading'`, `'resolved'`, `'error'`, `'reloading'`, `'local'`), que reemplazan las enumeraciones numéricas de Angular 19
- La opción `parse` de `httpResource` integra Zod o Valibot para una validación de esquemas en tiempo de ejecución que además dirige los tipos TypeScript
- Llamar a `value()` sobre una resource en estado de error lanza una excepción en Angular 20 — siempre proteger con `hasValue()` o comprobar `status()` primero
- Todas las variantes de resource cancelan automáticamente las solicitudes en curso cuando cambian las dependencias, evitando condiciones de carrera sin lógica de cancelación manual
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/es/blog/angular/angular-20-resource-api-httpresource-tutorial