Frontend Memory Leaks y como evitarlo en Angular
Tiempo estimado de lectura: 3 min
- Identifica suscripciones, listeners y timers que sobreviven a componentes.
- Prefiere async pipe y herramientas integradas (takeUntilDestroyed) para evitar fugas.
- Diagnostica con Chrome DevTools: heap snapshots y comparación de instancias.
- Disciplina de equipo: code review, linters y políticas de invalidación en singletons.
Introducción
¿Tu SPA se vuelve lenta sin error en consola? Frontend Memory Leaks y como evitarlo en Angular debería ser parte de tu checklist antes de enviar a producción. Si no controlas referencias, suscripciones y listeners, la app “come” memoria hasta que la pestaña muere.
Resumen rápido (lectores con prisa)
Qué es: Objetos no referenciados siguen vivos y el Garbage Collector no los elimina.
Cuándo usar: Siempre que tu SPA cree y destruya componentes, gestiona suscripciones, listeners y timers.
Por qué importa: Fugas causan degradación silenciosa de rendimiento en sesiones largas y móviles.
Cómo funciona: Evita referencias persistentes: async pipe, takeUntilDestroyed, Renderer2 y limpieza en ngOnDestroy.
Frontend Memory Leaks y como evitarlo en Angular: qué pasa y por qué importa
Un memory leak ocurre cuando objetos que ya no necesitas siguen referenciados y el Garbage Collector no los elimina. En Angular esto duele más: la app vive sin recargas, los componentes se crean y destruyen, y cualquier referencia residual se acumula en el heap.
No es magia ni culpa del navegador: es disciplina de código. Y sí, pasa en producción, en móviles y en equipos con pestañas cientos abiertas.
Fuentes útiles:
Vectores comunes y cómo cerrarlos
Suscripciones RxJS sin gestionar
- Qué pasa:
.subscribe()vive más que el componente. El callback mantiene la instancia viva. - Soluciones: Async pipe (templates),
takeUntilDestroyed()en Angular 16+, otakeUntilcon undestroy$en versiones anteriores. - Docs: RxJS takeUntil ; Angular rxjs-interop
Event listeners globales
Qué pasa: window.addEventListener('resize', fn) sin removeEventListener mantiene la referencia.
Solución: usar Renderer2.listen() (retorna un unlisten) o guardar la referencia y llamarla en ngOnDestroy().
Timers (setInterval, setTimeout)
Qué pasa: un timer que cierra sobre this impide la recolección.
Solución: almacenar el id y clearInterval/clearTimeout en ngOnDestroy().
Servicios singleton con datos retenidos
Qué pasa: providedIn: 'root' vive toda la sesión; si almacenas grandes estructuras sin limpieza, la memoria crece.
Solución: políticas explícitas de invalidación / límites / weak references lógicas.
Patrones prácticos (código)
Async pipe — la opción más simple
<!-- template -->
<div *ngIf="data$ | async as data">{{ data.title }}</div>
El async se suscribe y se desuscribe automáticamente cuando el componente muere.
takeUntilDestroyed() — Angular 16+ (recomendado para TS)
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
this.service.stream()
.pipe(takeUntilDestroyed())
.subscribe(x => this.handle(x));
Patrón clásico (Angular <16)
private destroy$ = new Subject<void>();
ngOnInit() {
this.service.stream()
.pipe(takeUntil(this.destroy$))
.subscribe(...);
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
Listeners con Renderer2
private unlisten: () => void;
ngOnInit() {
this.unlisten = this.renderer.listen('window', 'scroll', this.onScroll.bind(this));
}
ngOnDestroy() {
this.unlisten?.();
}
Timers
private intervalId?: ReturnType<typeof setInterval>;
ngOnInit() {
this.intervalId = setInterval(() => this.poll(), 5000);
}
ngOnDestroy() {
clearInterval(this.intervalId);
}
Cómo diagnosticar fugas — proceso práctico
- Abre Chrome DevTools → Memory (Chrome DevTools Memory).
- Toma Heap Snapshot.
- Navega al componente sospechoso, repite la interacción que crees filtra memoria.
- Regresa y toma otro snapshot.
- Compara: si ves instancias de tu componente o Detached DOM nodes, hay fuga.
Allocation timeline te muestra crecimiento en tiempo real: una línea que solo sube y no baja es una mala señal.
Política de equipo: lo que realmente previene fugas
Esto no es sólo técnica, es disciplina:
- Regla: preferir Async Pipe cuando sea posible.
- Regla: toda suscripción en clase debe documentarse: ¿se cancela? ¿por quién?
- Code reviews: busca
.subscribe(sintakeUntil/takeUntilDestroyedo sin async. - Linters y tareas CI: detecta patrones peligrosos (por ejemplo
.subscribe(sinunsubscribeen componentes). - Tests de rendimiento en CI: integra un paso de e2e que simule navegación repetida y monitorice memoria.
Cierre: no cures síntomas, cambia hábitos
Un memory leak no es un bug aislado; es deuda técnica que crece silenciosamente. Angular te da herramientas —async pipe, takeUntilDestroyed, Renderer2— pero depende del equipo usarlas consistentemente.
Si quieres, en Dominicode podemos publicar una checklist de code-review y un snippet de ESLint que detecte suscripciones peligrosas. No te prometo magia: te prometo menos pestañas explotando. Apúntate y seguimos.
FAQ
- ¿Qué es un memory leak?
- ¿Cómo detectarlo en Angular?
- ¿Cómo manejar suscripciones RxJS?
- ¿Y los listeners globales?
- ¿Qué hacer con timers e intervalos?
- ¿Los servicios singleton pueden filtrar memoria?
¿Qué es un memory leak?
Un memory leak ocurre cuando objetos que ya no necesitas siguen referenciados y el Garbage Collector no los elimina. En una SPA esto provoca acumulación de memoria en el heap mientras la sesión continúa.
¿Cómo detectarlo en Angular?
Usa Chrome DevTools → Memory: toma heap snapshots antes y después de interacciones repetidas. Busca instancias de componentes o Detached DOM nodes que no desaparecen.
¿Cómo manejar suscripciones RxJS?
Prefiere async en templates, o en código use takeUntilDestroyed() (Angular 16+) o takeUntil con un destroy$ y limpieza en ngOnDestroy().
¿Y los listeners globales?
No uses addEventListener sin remover. Usa Renderer2.listen() y llama al “unlisten” en ngOnDestroy().
¿Qué hacer con timers e intervalos?
Guarda los ids retornados por setInterval/setTimeout y llama a clearInterval/clearTimeout en ngOnDestroy().
¿Los servicios singleton pueden filtrar memoria?
Sí. Los servicios providedIn: 'root' viven toda la sesión; si almacenan grandes estructuras sin limpieza la memoria crece. Define políticas de invalidación y límites.

Leave a Reply