Para leer (GET), httpResource
Carga datos del servidor y le entrega value(), isLoading() y error() listos para
usar en la plantilla.
Dos herramientas, un rol cada una. Una le ofrece el estado reactivo, la otra le da control total sobre las acciones. Saber cuándo usar cada cual es lo que separa un componente que funciona de uno que escala.
Para leer (GET), httpResource
Carga datos del servidor y le entrega value(), isLoading() y error() listos para
usar en la plantilla.
Para actuar (POST / PUT / DELETE), HttpClient
Ejecuta cualquier método HTTP. Usted maneja cuándo se dispara y qué pasa con la respuesta. Imprescindible para crear, editar y borrar.
Ya tiene sus interfaces TypeScript, ya tiene un arreglo de prueba en el componente. Pero la realidad es que los datos viven en un servidor (un Gist de GitHub hoy, un back en Spring más adelante). Hay que ir a buscarlos.
Sin Angular, haría algo así con el API nativa del navegador:
// Usted gestiona TODO: carga, error, datoslet films = [];let isLoading = false;let error = null;
fetch('data/films.json') .then(r => r.json()) .then(data => { films = data; isLoading = false; }) .catch(err => { error = err; isLoading = false; });// Problema: ¿cómo aviso a la vista que cambió el estado?La estructura de datos que se usa en todos los ejemplos es la del archivo films.json del proyecto FilmSignals. Así se ve un elemento:
{ "title": "Interstellar", "year": "2014", "director": "Christopher Nolan", "imdbRating": "8.6", "imdbID": "tt0816692", "poster": "https://..."}Y la interfaz TypeScript que lo modela, el contrato que se firma con el compilador:
export interface Film { title: string; year: string; director: string; imdbRating: string; imdbID: string; poster: string;}httpResource es una función de Angular (disponible desde v19) que hace el GET y le
entrega el resultado ya empaquetado en signals. No hay subscribe, no hay manejo manual de
estados.
Esto es todo lo que necesita para traer la lista de películas y conectarla a su plantilla:
import { Injectable, inject } from '@angular/core';import { httpResource } from '@angular/common/http';import { Film } from './film';
@Injectable({ providedIn: 'root' })export class FilmService {
// Una sola línea: URL → signals listas para usar filmsResource = httpResource<Film[]>('data/films.json');
}flowchart LR
call["httpResource<Film[]>('data/films.json')"] --> obj
subgraph obj["objeto que devuelve: filmsResource"]
direction TB
v["value()<br/>Film[] | undefined"]
l["isLoading()<br/>boolean"]
e["error()<br/>unknown | undefined"]
end
httpResource devuelve un objeto con varios signals. Los tres que usará siempre en la
plantilla son:
| Signal | Tipo | ¿Qué contiene? | Cuándo usarlo |
|---|---|---|---|
.value() |
Film[] | undefined |
Los datos cuando ya llegaron. undefined mientras carga. |
Pintar la lista con @for |
.isLoading() |
boolean |
true mientras la petición está en vuelo. |
Mostrar un spinner o texto “Cargando…” |
.error() |
unknown | undefined |
El error si la petición falló. undefined en caso de éxito. |
Mostrar un mensaje de error con @if |
<!-- Mientras carga -->@if (filmService.filmsResource.isLoading()) { <p>Cargando películas…</p>}
<!-- Si hubo error -->@if (filmService.filmsResource.error()) { <p class="error">Error al cargar. Intente de nuevo.</p>}
<!-- Los datos cuando llegan -->@for (film of filmService.filmsResource.value() ?? []; track film.imdbID) { <div class="card">{{ film.title }}</div>}Simule lo que Angular haría al ejecutar httpResource('data/films.json'):
isLoading(): falsevalue(): undefinederror(): undefinedhttpResource es perfecta para leer. Pero si el usuario quiere crear una película nueva,
editarla o borrarla, necesita enviar datos al servidor, y para eso existe HttpClient.
GET, httpResource
Leer la lista completa. Angular gestiona carga y error automáticamente.
POST, HttpClient
Enviar un nuevo objeto al servidor. Usted define qué pasa al recibir la respuesta.
PUT, HttpClient
Reemplazar un objeto existente por su id.
DELETE, HttpClient
Eliminar un recurso del servidor por su id.
import { Injectable, inject } from '@angular/core';import { HttpClient } from '@angular/common/http';import { Film } from './film';
@Injectable({ providedIn: 'root' })export class FilmService {
// 1. Inyecta HttpClient, igual que en la sesión 2 de R1 private http = inject(HttpClient); private apiUrl = 'https://mi-api.com/films';
// 2. El método devuelve un Observable, no ejecuta nada todavía createFilm(newFilm: Film) { return this.http.post<Film>(this.apiUrl, newFilm); }}export class FilmFormComponent {
private filmService = inject(FilmService);
onSubmit(formData: Film) {
// 3. subscribe() es quien activa la petición HTTP this.filmService.createFilm(formData).subscribe({ next: (creada) => { // 4a. Éxito: la API devolvió la película creada console.log('Película creada:', creada); }, error: (err) => { // 4b. Error: algo salió mal en el servidor console.error('Error al crear:', err); } }); }}Usuario hace clic en "Guardar" ↓Componente llama filmService.createFilm(datos) ↓Servicio llama http.post(url, datos) ← Observable creado, no disparado ↓Componente llama .subscribe() ← Aquí se envía la petición ↓Servidor procesa y responde ↓next() o error() se ejecutan ↓Componente actualiza el estado (signal) → vista reaccionaRellene los campos y observe qué JSON se enviaría al servidor y qué respuesta llegaría:
{
"title": "",
"year": "",
"director": "",
"imdbRating": ""
}La respuesta del servidor aparecerá aquí…
// Reemplaza la película con ese imdbIDupdateFilm(id: string, updated: Film) { return this.http.put<Film>(`${this.apiUrl}/${id}`, updated);}// Borra la película, generalmente no devuelve cuerpodeleteFilm(id: string) { return this.http.delete(`${this.apiUrl}/${id}`);}// En el componente, igual: .subscribe({ next: ..., error: ... })En la práctica, un servicio Angular que maneja una entidad usa los dos: httpResource para
la lista (siempre actualizada) y HttpClient para las operaciones que modifican datos.
// La lista, siempre reactivafilmsResource = httpResource<Film[]>('api/films');
// En la plantilla:// films.value() → los datos// films.isLoading() → spinner// films.error() → mensaje// Crearcreate(f: Film) { return http.post('api/films', f);}// Editarupdate(id, f: Film) { return http.put(`api/films/${id}`, f);}// Borrarremove(id) { return http.delete(`api/films/${id}`);}httpResource después de escribir, .reload()Después de crear, editar o borrar algo con HttpClient, la lista que trajo
httpResource queda desactualizada: sigue mostrando lo que había antes de la
escritura. httpResource resuelve esto con un método propio, .reload(), que vuelve a
disparar el mismo GET y actualiza value() con la respuesta nueva.
onSubmit(formData: Film) { this.filmService.createFilm(formData).subscribe({ next: () => { // El POST ya terminó: la lista que muestra otro componente // sigue mostrando los datos viejos hasta que se refresque. this.filmService.filmsResource.reload(); }, error: (err) => console.error('Error al crear:', err), });}// ✓ El servicio es el único que habla con la red// ✓ El componente llama métodos del servicio// ✗ NUNCA inyecte HttpClient directamente en un componente
// La presentación no sabe de dónde vienen los datos.// Si mañana cambia el back, solo se toca el servicio.