¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
dev
Eider Ogueta Hace 1 hora Cargando comentarios…
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.
En el primer artículo vimos que Angular utilizaba Zone.js para interceptar operaciones asíncronas:
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.
Sigamos exactamente el mismo ejemplo de los artículos anteriores. Tenemos:
@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:
Aunque los usuarios no hubieran cambiado.
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
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”.
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:
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?
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.
Zone.js parchea APIs del navegador para interceptar tareas asíncronas:
Cuando cualquiera de esas tareas termina, Angular ejecuta detección de cambios global. El problema es que eso tiene costes:
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.
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:
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.
Actualmente, Angular permite habilitarlo mediante:
bootstrapApplication(AppComponent, {
providers: [
provideZonelessChangeDetection()
]
});
Y eliminando:
npm uninstall zone.js
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?:
Ya no existe ese “revisar toda la aplicación por si acaso”.
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:
Las tres piezas se complementan y, de hecho, la propia documentación oficial recomienda OnPush como paso natural hacia compatibilidad zoneless.
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.
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:
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.
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 |
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 |
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:
De hecho, Angular insiste bastante en que el futuro pasa por Signals, async pipe, OnPush, APIs reactivas y notificaciones explícitas al framework.
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.
Artículos previos de la serie
Documentación oficial de Angular
Artículos y análisis técnicos
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.
Cuéntanos qué te parece.