CodeForge

Angular 21 de Cero a Experto / HTTP y datos

Caché: no pedir dos veces lo mismo

Teoría20 min20 XP

Pedirle al servidor la lista de categorías cada vez que se abre una pantalla es un desperdicio: si no ha cambiado, ¿para qué volver a traerla? La CACHÉ guarda respuestas para reutilizarlas, haciendo tu app más rápida y ahorrando peticiones. Hoy aprendes a cachear con shareReplay, a construir un servicio de caché con expiración, y —lo más importante— a saber cuándo invalidarla para no mostrar datos viejos.

La despensa con fecha de caducidad

Caché simple con shareReplay

Para una petición que varios lugares consumen y que no cambia seguido, shareReplay(1) convierte el Observable en compartido y recordado —una sola petición para todos—:

categorias.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { shareReplay } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class CategoriasService {
private http = inject(HttpClient);

// se pide UNA vez; todos los suscriptores reutilizan el resultado
readonly categorias$ = this.http.get<string[]>('/api/categorias').pipe(
  shareReplay(1),
);
}

Como el servicio es un singleton (providedIn: 'root') y shareReplay(1) recuerda el último valor, la petición se hace la primera vez que alguien se suscribe y luego se reutiliza. Perfecto para datos estables: categorías, configuración, listas de referencia.

Un servicio de caché con expiración

Para más control (expiración, invalidación por clave), un servicio de caché propio con un Map:

cache.service.ts
import { Injectable } from '@angular/core';

interface Entrada<T> { valor: T; expira: number; }

@Injectable({ providedIn: 'root' })
export class CacheService {
private cache = new Map<string, Entrada<unknown>>();

get<T>(clave: string): T | null {
  const entrada = this.cache.get(clave);
  if (!entrada) return null;
  if (Date.now() > entrada.expira) {   // caducada → fuera
    this.cache.delete(clave);
    return null;
  }
  return entrada.valor as T;
}

set<T>(clave: string, valor: T, ttlMs = 60_000) {
  this.cache.set(clave, { valor, expira: Date.now() + ttlMs });
}

invalidar(clave: string) { this.cache.delete(clave); }
limpiar() { this.cache.clear(); }
}

Con TTL (time to live), cada entrada caduca sola tras cierto tiempo: no muestras datos eternamente viejos. El servicio consulta la caché antes de pedir, y guarda la respuesta.

El problema difícil: invalidar

Pruébalo: una caché con TTL, en vivo

La lógica de una caché —guardar con expiración, servir si está fresca, invalidar— es pura. Aquí, funcionando:

Observa: la 1ª lectura pide al servidor y cachea; la 2ª y 3ª sirven de la caché (sin petición). Cuando el usuario crea un gasto, INVALIDAMOS la clave, así la 4ª lectura vuelve a pedir datos frescos. Sin ese invalidar, seguiríamos mostrando la lista vieja —el bug clásico de caché—. Las stats muestran cuántas peticiones te ahorró la caché.

Cacheas la lista de gastos. El usuario crea un gasto nuevo (POST). ¿Qué debes hacer para que no vea datos viejos?

Mini-reto

En el playground: 1) agrega un método obtenerCategorias() con su propia clave y un TTL más largo (categorías cambian poco); 2) simula que el usuario edita un gasto e invalida solo 'gastos' (no las categorías); 3) verifica con las stats que las categorías siguen sirviendo de caché mientras los gastos se repidieron. Piensa qué TTL le pondrías a datos que casi nunca cambian vs a datos muy dinámicos.

Qué sigue

Manejas peticiones, estados, interceptores y caché a mano. Angular moderno empaqueta muchos de estos patrones en una sola herramienta declarativa: httpResource. La próxima lección la presenta —cargar datos con estados y recarga reactiva en pocas líneas—, y cierra la teoría del módulo antes del reto.