Hace un tiempo publicamos dos artículos donde recorríamos la evolución de la detección de cambios en Angular.

Primero entendimos cómo Angular era capaz de “saber” cuándo actualizar la UI gracias a Zone.js, el árbol de componentes y las estrategias de detección de cambios. Después dimos el salto a Signals y vimos cómo Angular comenzaba a actualizar únicamente aquello que realmente dependía del estado reactivo.

En este tercer capítulo vamos a cerrar el círculo. Porque si Signals solucionaba qué debe actualizarse… Zoneless cambia completamente cuándo Angular decide ejecutar la detección de cambios.

Y sí: Angular ya puede funcionar sin Zone.js.

Del “Angular lo detecta todo” al “Angular detecta solo lo necesario”

En el primer artículo vimos que Angular utilizaba Zone.js para interceptar operaciones asíncronas:

  • Clicks
  • setTimeout
  • Peticiones HTTP
  • Promesas
  • Eventos del navegador…

Cada vez que ocurría cualquiera de esas operaciones, Angular lanzaba un ciclo de detección de cambios completo. El problema es que Angular realmente no sabía si había cambiado algo relevante.

Simplemente asumía:

“Ha ocurrido algo asíncrono. Por si acaso… reviso toda la aplicación”.

Y eso funcionaba muy bien, pero también implicaba trabajo innecesario.

El ejemplo del reloj

Sigamos exactamente el mismo ejemplo de los artículos anteriores. Tenemos:

  • Un reloj que se actualiza cada segundo
  • Una lista de usuarios.
@Component({
 selector: 'app-root',
 template: `
   <h2>{{ clock }}</h2>

   @for (user of users; track user.id) {
     <app-user-row [user]="user"></app-user-row>
   }
 `
})
export class AppComponent {
 clock = '';

 users = USERS;

 ngOnInit() {
   setInterval(() => {
     this.clock = new Date().toLocaleTimeString();
   }, 1000);
 }
}

En el primer artículo vimos que:

  • Cada segundo, Angular recorría el árbol completo
  • Recalculaba bindings
  • Ejecutaba expresiones
  • Revisaba todos los componentes

Aunque los usuarios no hubieran cambiado.

OnPush fue el primer parche

Después apareció ChangeDetectionStrategy.OnPush.

@Component({
 changeDetection: ChangeDetectionStrategy.OnPush
})

Con OnPush, Angular dejaba de revisar componentes “porque sí” y solo los comprobaba cuando:
Cambiaba un @Input

  • Ocurría un evento dentro del componente
  • Se emitía un observable con async
  • O llamábamos manualmente a markForCheck()

Era mucho más eficiente, pero seguíamos dependiendo de Zone.js porque Angular seguía necesitando un mecanismo global que dijera:

“Oye, acaba de pasar algo asíncrono”.

Signals cambió las reglas del juego

Y aquí llegó la gran revolución. Con Signals, Angular ya no necesita “preguntarse” qué ha cambiado, ahora lo sabe.

clock = signal('');
setInterval(() => {
 this.clock.set(new Date().toLocaleTimeString());
}, 1000);

En la plantilla:

<h2>{{ clock() }}</h2>

Angular registra automáticamente qué partes de la UI dependen de cada signal y, cuando el signal cambia:

  • Angular marca únicamente los componentes afectados
  • Evita recorrer ramas innecesarias
  • Actualiza solo los bindings dependientes

Esto ya suponía un salto enorme de rendimiento, pero todavía quedaba una pregunta importante: si Signals ya sabe exactamente qué cambia… ¿para qué seguimos necesitando Zone.js?

Aquí entra Zoneless

La respuesta corta es que ya no lo necesitamos tanto. Angular empezó a introducir soporte para aplicaciones “zoneless”, es decir, aplicaciones sin Zone.js y eso cambia completamente el modelo mental.

¿Qué hace realmente Zone.js?

Zone.js parchea APIs del navegador para interceptar tareas asíncronas:

  • Timers
  • Eventos
  • Promesas
  • XHR
  • Fetch
  • etc.

Cuando cualquiera de esas tareas termina, Angular ejecuta detección de cambios global. El problema es que eso tiene costes:

  • Más trabajo innecesario
  • Más ciclos de detección
  • Peor startup
  • Stack traces más difíciles
  • Y más complejidad interna

De hecho, uno de los objetivos principales de Zoneless es mejorar el rendimiento, el Core Web Vitals, la compatibilidad con APIs modernas y la experiencia de debugging.

¿Cómo funciona Angular sin Zone.js?

En modo zoneless, Angular deja de “espiar” todo el navegador. En lugar de eso, solo actualiza la UI cuando recibe notificaciones explícitas. Por ejemplo:

  • Un signal cambia,
  • Un AsyncPipe emite
  • Ocurre un evento Angular ((click))
  • Se llama a markForCheck()
  • Un @Input recibe un nuevo valor

Es decir: Angular ya no hace polling implícito del estado. Ahora el propio framework sabe exactamente cuándo algo relevante ha cambiado y aquí está probablemente la idea más importante de todo el cambio: Angular pasa de un modelo reactivo implícito a uno explícito.

Activando Zoneless

Actualmente, Angular permite habilitarlo mediante:

bootstrapApplication(AppComponent, {
 providers: [
   provideZonelessChangeDetection()
 ]
});

Y eliminando:

npm uninstall zone.js

Volvamos al ejemplo del reloj

Ahora, nuestro ejemplo cambia bastante.

@Component({
 selector: 'app-root',
 template: `
   <h2>{{ clock() }}</h2>
   @for (user of users(); track user.id) {
     <app-user-row [user]="user"></app-user-row>
   }
 `})
export class AppComponent {
 clock = signal('');
 users = signal(USERS);

 ngOnInit() {
   setInterval(() => {
     this.clock.set(
       new Date().toLocaleTimeString()
     );
   }, 1000);
 }
}

¿Qué ocurre ahora cada segundo?

¿Qué ocurre ahora cada segundo?:

  • Cambia únicamente clock
  • Angular marca únicamente ese binding
  • La tabla de usuarios ni siquiera entra en el ciclo.

Ya no existe ese “revisar toda la aplicación por si acaso”.

Entonces… ¿Signals sustituye a OnPush?

No exactamente. Signals y Zoneless mejoran muchísimo cómo Angular programa la detección de cambios, pero OnPush sigue siendo importante.

Porque Zoneless no cambia cómo Angular recorre el árbol de componentes, lo que cambia es cuándo decide lanzar detección de cambios.

Así que:

  • OnPush sigue ayudando a limitar comprobaciones
  • Signals sigue marcando componentes concretos
  • Zoneless evita disparar ciclos globales innecesarios.

Las tres piezas se complementan y, de hecho, la propia documentación oficial recomienda OnPush como paso natural hacia compatibilidad zoneless.

Algo importante: Angular sigue detectando eventos

Hay un detalle muy interesante. Aunque eliminemos Zone.js, esto sigue funcionando:

<button (click)="increment()">
 Incrementar
</button><br>
counter++;

¿Por qué? Porque los eventos registrados a través de Angular siguen notificando al framework automáticamente.

Pero cuidado: esto NO ocurre con APIs externas al ecosistema Angular. Por ejemplo:

element.addEventListener('click', () => {
 this.counter++;
});

Aquí, Angular ya no sabe que algo cambió y necesitaríamos:

markForCheck()

O utilizar Signals.

Lo que empieza a “romperse” en Zoneless

Aquí es donde la teoría se vuelve interesante, porque la mayoría de aplicaciones Angular actuales dependen indirectamente de comportamientos automáticos de Zone.js.

Y cuando lo eliminamos… aparecen ciertas sorpresas. Por ejemplo, este patrón clásico deja de funcionar correctamente:

this.userService.users$
 .subscribe(users => {
   this.users = users;
 });

Si luego mostramos users directamente en plantilla, Angular podría no enterarse del cambio porque la suscripción ocurre fuera de cualquier mecanismo reactivo observable para Angular.

La solución moderna pasa por:

  • Usar async pipe
  • Convertir observables a signals
  • Llamar explícitamente a markForCheck().

Por ejemplo:

users = toSignal(this.userService.users$);

Aquí empieza a verse claramente hacia dónde quiere ir Angular: menos magia implícita y más reactividad explícita.

Otro detalle importante es Reactive Forms. Operaciones como form.patchValue(...)siguen actualizando el estado interno del formulario…pero ya no fuerzan automáticamente detección de cambios.

¿Desde qué versión existe Zoneless?

Angular ha ido evolucionando esta capacidad durante varias versiones.

Versión Estado
Angular 17.1 Primeras APIs internas experimentales (ɵprovideZonelessChangeDetection)
Angular 18 Soporte experimental oficial mediante provideExperimentalZonelessChangeDetection()
Angular 20.2 API estable provideZonelessChangeDetection()
Angular 21+ Zoneless pasa a ser el comportamiento por defecto

El verdadero cambio de paradigma

Angular deja atrás un modelo basado en “revisar por si acaso” para pasar a un modelo basado en reactividad explícita.

Mucho más predecible, mucho más eficiente y bastante más cercano a cómo funcionan frameworks modernos como Solid o Vue Signals.

Etapa Qué ocurría
Angular clásico Angular revisa todo constantemente
OnPush Angular revisa menos componentes
Signals Angular sabe exactamente qué cambió
Zoneless Angular sabe exactamente cuándo reaccionar

¿Está listo para producción?

A día de hoy, Zoneless ya forma parte de la estrategia oficial de Angular y el framework está claramente orientado hacia este modelo reactivo, pero eso no significa que cualquier aplicación pueda eliminar Zone.js mañana sin más. Conviene validar cuidadosamente:

  • Librerías de terceros
  • Integraciones DOM manuales
  • Código legacy
  • Formularios reactivos
  • SSR
  • Testing
  • Patrones basados en side effects implícitos

De hecho, Angular insiste bastante en que el futuro pasa por Signals, async pipe, OnPush, APIs reactivas y notificaciones explícitas al framework.

Conclusiones

Durante años, Angular se apoyó en Zone.js para detectar cualquier posible cambio en la aplicación. Después llegó OnPush para reducir trabajo innecesario. Más tarde apareció Signals para permitir una reactividad mucho más precisa.

Y ahora Zoneless termina de cerrar esa evolución eliminando la necesidad de vigilar constantemente todo lo que ocurre en el navegador.

La combinación de Signals, OnPush, y Zoneless nos permite construir aplicaciones mucho más eficientes, predecibles y fáciles de razonar.

Pero también obliga a entender mucho mejor cómo funciona realmente la reactividad del framework. Porque Angular ya no intenta adivinar lo que ocurre, ahora espera que seamos explícitos y, probablemente, ahí está el cambio más importante de todos.

Referencias

Artículos previos de la serie

Documentación oficial de Angular

Artículos y análisis técnicos

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.