Angular está mejor que nunca
Kevin Dávila
Hubo un momento, allá por 2021-2022 (de hecho, desde 2016), donde en Twitter los tech Bros decían todos los días: "Angular está muriendo. React ganó. Vue es más simple. Svelte es el futuro."
Y bueno, parte de esa crítica era justa. Angular tenía una curva de aprendizaje enorme, los NgModules eran confusos hasta para devs experimentados, Zone.js hacía magia negra con tus async functions, y el bundle size de una app "Hello World" daba tristeza.
Pero algo pasó entre 2022 y 2025 que muchos no vieron venir: el equipo de Angular se puso a trabajar en serio. No con tweaks cosméticos, sino con cambios arquitectónicos de fondo. Minko Gechev, el Product Manager de Angular en Google (de aquella época), empezó a hablar de un "Angular Renaissance" — y no era marketing vacío.
Este blog es para contarte qué cambió, por qué importa, y por qué si no has revisado Angular desde hace dos o tres años, probablemente estás trabajando con una imagen mental equivocada del framework.

1. Standalone Components: adiós a los horribles NgModules
Si alguna vez le explicaste Angular a alguien nuevo, sabes exactamente qué cara ponen cuando ven AppModule, declarations, imports, exports, y providers juntos por primera vez. Es como si el framework te pidiera declarar todo en un formulario de impuestos antes de poder mostrar un botón.
En Angular v14 (junio 2022) llegaron los Standalone Components, y en Angular v19 (noviembre 2024) se convirtieron en el default. Hoy, cuando generas un componente con la CLI, ya no hay módulo. Solo el componente.
// app.component.ts
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
standalone: true, // Hoy poner esto ya no es necesario
imports: [RouterOutlet],
template: `<router-outlet />`,
})
export class AppComponent {}
Alex Rickabaugh del equipo core de Angular lo dijo sin rodeos: "The future is standalone". Y la comunidad lo adoptó masivamente — una encuesta de 2024 con más de 12,000 respuestas mostró que el 96% de los devs ya usaba las nuevas APIs. - Source: https://blog.angular.dev/the-future-is-standalone-475d7edbc706
Los NgModules no están deprecados — siguen funcionando, y puedes mezclar ambos estilos en la misma app. Pero la documentación oficial ahora enseña standalone desde el primer día.
2. Signals: el sistema reactivo que Angular necesitaba desde hace años
Este es el más importante de todos, así que vale la pena explicarlo bien.
Angular siempre detectó cambios usando Zone.js, una librería que básicamente hace monkey-patch a todas las APIs asíncronas del navegador (setTimeout, Promise, fetch...) para saber cuándo algo cambió y cuándo re-renderizar. Funciona, pero tiene un costo: cuando algo cambia, Angular por defecto revisa toda la árbol de componentes. TODO EL ÁRBOL.
Imagina esto en apps grandes, es tirar el performance a la basura.
Angular v16 (mayo 2023) trajo Signals como developer preview. Angular v17 (noviembre 2023) los graduó a estables.
La idea central es que un Signal es un valor que sabe quién lo está leyendo. Cuando cambia, solo notifica a quienes dependen de él. Nada más. Nada de recorrer 400 componentes para ver si algo cambió.
// counter.component.ts
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-counter',
standalone: true,
template: `
<p>Count: {{ count() }}</p>
<p>Double: {{ double() }}</p>
<button (click)="increment()">+1</button>
`,
})
export class CounterComponent {
count = signal(0);
double = computed(() => this.count() * 2);
increment() {
this.count.update(v => v + 1);
}
}
Lo que hace especial a esto no es solo la sintaxis más limpia. Es que Pawel Kozlowski del equipo core explicó el enfoque "glocal" — cuando un signal cambia, solo se re-renderiza ese componente específico, no todo el árbol. Eso tiene un impacto directo en INP (Interaction to Next Paint), una de las Core Web Vitals que Google usa para rankear sitios. - Source: https://angular.dev/guide/signals
Y hay algo más grande, el equipo de Angular participó en la propuesta TC39 Stage 1 para llevar Signals directamente al lenguaje JavaScript. Angular como referencia de implementación de una feature que podría llegar al browser nativo. Eso sí que es una declaración de intenciones. (En tu cara, Svelte)
3. Nueva Template Syntax: @if, @for, @switch
Si Signals son el cambio más profundo internamente, la nueva sintaxis de templates es el que más vas a notar en el día a día.
Antes, para mostrar algo condicionalmente necesitabas importar NgIf o CommonModule, usar la sintaxis de microsyntax *ngIf="condition", y rezar para que TypeScript infiriera bien los tipos. Con @for, además tenías que acordarte del trackBy o sufrir con el performance.
Angular v17 introdujo el nuevo Control Flow nativo directamente en los templates:
<!-- hero-list.component.html -->
@if (isLoading()) {
<app-skeleton-loader />
} @else if (heroes().length === 0) {
<p>No heroes found.</p>
} @else {
@for (hero of heroes(); track hero.id) {
<app-hero-card [hero]="hero" />
} @empty {
<p>The list is empty.</p>
}
}
Tres cosas que esto mejora de golpe:
Performance: Los benchmarks mostraron que @if es un 45% más rápido que *ngIf (0.82ms vs 1.49ms en refreshView). La renderización inicial mejora entre un 10-20% en vistas que los usan intensamente. - Source: https://dev.to/raju_dandigam/template-revolution-angulars-new-syntax-that-you-should-be-using-today-404m
Bundle size: Los bloques de @switch que no se usan se tree-shakean en producción. El saving puede llegar a 30KB menos en el bundle final. - Source: https://www.infragistics.com/blogs/control-flow-in-angular-17/
DX: Ya no necesitas importar nada. El control flow está disponible en cualquier template sin declaraciones adicionales. TypeScript también hace mejor type narrowing dentro de los bloques @if.
El @empty dentro de @for merece mención especial — cuántas veces escribiste un *ngIf="items.length === 0" extra para el estado vacío. Ya no hace falta.
Aquí debo mencionar que, en un inicio, yo rechazaba activamente la nueva sintaxis. Estuve como viejito gruñon criticando a los jóvenes y sus sintaxis rebeldes.
Pero luego probé la nueva versión y wow, es un cambio tan fácil y agradable que ni extrañas la antigua.
Punto para Angular.
4. Deferrable Views (@defer): lazy loading que tiene sentido
Una de las quejas clásicas de Angular era que cargar código de forma lazy era un proceso bastante manual — o lo hacías a nivel de rutas, o te las ingeniabas con loadComponent. No había una forma declarativa de decir "este componente cárgalo solo cuando el usuario lo vea".
Angular v17 trajo @defer y cambió todo eso:
<!-- dashboard.component.html -->
@defer (on viewport) {
<app-heavy-analytics-chart />
} @placeholder {
<div class="chart-placeholder">Cargando gráfica...</div>
} @loading (minimum 300ms) {
<app-spinner />
} @error {
<p>Error cargando el componente.</p>
}
El componente dentro del bloque @defer se separa automáticamente en su propio chunk de JavaScript. No se descarga hasta que el trigger se activa. Los triggers disponibles son:
on idle — cuando el browser está libre (default)
on viewport — cuando el elemento entra al viewport (Intersection Observer)
on interaction — cuando el usuario interactúa
on timer(2s) — después de un delay
when condition — cuando una condición se vuelve true
Para que funcione el lazy loading, los componentes dentro del bloque deben ser standalone. Pero dado que standalone ya es el default, esto ya no es un obstáculo. - Source: https://angular.dev/guide/templates/defer
En dashboards o páginas de administración con muchos widgets pesados, esto puede reducir el bundle inicial de forma dramática. Solo se descarga lo que el usuario realmente necesita, cuando lo necesita.
Que el framework incluya esto de una manera tan simple es un golazo.
5. Signal Inputs, Outputs y model(): el fin de los decoradores
Una vez que Signals estuvieron estables, el equipo fue más allá: reemplazar los decoradores @Input() y @Output() con funciones basadas en signals.
// product-card.component.ts
import { Component, input, output, model } from '@angular/core';
import { Product } from './product.model';
@Component({
selector: 'app-product-card',
standalone: true,
template: `
<h3>{{ product().name }}</h3>
<p>Price: {{ product().price }}</p>
<button (click)="addToCart.emit(product())">Add to cart</button>
<input [value]="quantity()" (input)="quantity.set(+$event.target.value)" />
`,
})
export class ProductCardComponent {
product = input.required<Product>(); // como @Input() obligatorio
addToCart = output<Product>(); // como @Output()
quantity = model(1); // two-way binding: @Input + @Output juntos
}
input() retorna una señal de solo lectura. output() retorna un OutputEmitterRef. Pero el más interesante es model() — define automáticamente tanto el input como el output para two-way binding, perfecto para componentes de formulario como date pickers, selects o comboboxes personalizados. - Source: https://angular.dev/guide/signals
Los decoradores @Input() y @Output() siguen funcionando. Pero la comunidad ya está adoptando el estilo funcional porque es más limpio, tiene mejor inferencia de tipos, y se integra naturalmente con el resto del ecosistema de signals.
6. Hydration: SSR que por fin no rompe el DOM
El Server-Side Rendering de Angular siempre tuvo un problema clásico: al llegar el JavaScript al cliente, Angular destruía el HTML que el servidor había generado y lo reconstruía desde cero. Resultado: un flash de contenido, CLS alto, y LCP que no era tan bueno como parecía.
Angular v16 introdujo non-destructive hydration en developer preview. Angular v17 la graduó a estable. El cambio conceptual es simple pero poderoso: en lugar de tirar el DOM del servidor y reconstruirlo, Angular reutiliza los nodos existentes y solo "hidrata" los event listeners y el estado de los componentes.
El impacto en métricas fue real: los proyectos que migraron reportaron consistentemente mejoras del 40-50% en LCP (Largest Contentful Paint). - Source: https://www.telerik.com/blogs/new-angular-hydration
Angular v18 fue más allá con Event Replay — si el usuario hace click en algo antes de que la hidratación termine, el evento se captura y se reproduce cuando el componente ya está listo. Ya no hay clicks perdidos en el SSR.
Angular v19 trajo Incremental Hydration en developer preview (y estable en v20), construida sobre los bloques @defer:
// app.config.ts
import { provideClientHydration, withIncrementalHydration } from '@angular/platform-browser';
export const appConfig: ApplicationConfig = {
providers: [
provideClientHydration(withIncrementalHydration())
]
};
<!-- En el template -->
@defer (hydrate on viewport) {
<app-product-recommendations />
}
Esto significa que puedes tener partes de la página que se sirven como HTML estático y solo se hidratan cuando el usuario realmente las necesita. Para e-commerce y sitios de contenido, esto es enorme. - Source: https://www.syncfusion.com/blogs/post/incremental-hydration-in-angular-apps
7. inject()
¿Cuántas veces viste un constructor así?
// old-way.component.ts
constructor(
private userService: UserService,
private authService: AuthService,
private router: Router,
private store: Store<AppState>,
private analyticsService: AnalyticsService,
private featureFlagService: FeatureFlagService
) {}
(Si lo viste en mis repositorios, no me respondas)
La función inject() llegó en Angular v14 para resolver exactamente esto:
// modern.component.ts
import { Component, inject } from '@angular/core';
import { UserService } from './user.service';
import { AuthService } from './auth.service';
import { Router } from '@angular/router';
@Component({ standalone: true, template: '' })
export class ModernComponent {
private userService = inject(UserService);
private authService = inject(AuthService);
private router = inject(Router);
}
Pero el poder real de inject() no es solo la limpieza visual. Es que permite crear funciones helper reutilizables que encapsulan lógica con dependencias:
// use-current-user.ts
import { inject } from '@angular/core';
import { AuthService } from './auth.service';
import { computed } from '@angular/core';
export function useCurrentUser() {
const auth = inject(AuthService);
return {
user: auth.currentUser,
isAdmin: computed(() => auth.currentUser()?.role === 'admin'),
logout: () => auth.logout(),
};
}
Esto es un patrón similar a los hooks de React, pero para Angular. Reutilizable, testeable, y sin herencia de clases ni boilerplate de módulos. También es la única opción cuando trabajas con functional guards en el router. - Source: https://angular.dev/guide/dependency-injection
8. Zoneless Angular: el final de la magia negra
Zone.js fue durante años la pieza más opaca de Angular. Para que la detección de cambios funcionara automáticamente, monkeypatcheaba setTimeout, Promise, fetch, addEventListener, y prácticamente cualquier API asíncrona del browser. Funcionaba, pero tenía costos:
Payload adicional en el bundle
Debugging complicado cuando algo fallaba silenciosamente
Performance hits en apps con muchas operaciones async
Angular v17.1 introdujo las primeras APIs experimentales para modo zoneless. Angular v20 lo llevó a producción estable.
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideZonelessChangeDetection } from '@angular/core';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
providers: [provideZonelessChangeDetection()]
});
Sin Zone.js, la detección de cambios la mueven los Signals, los template events, el async pipe, o checks manuales cuando son necesarios. El resultado: startup más rápido, bundle más pequeño, y debugging limpio sin la indirección de Zone.
En Angular v21, Zoneless se convierte en el default para nuevas apps. Para proyectos existentes, la migración es gradual — puedes activarlo y encontrar qué partes de tu código dependían de la magia de Zone. - Source: https://angular.dev/guide/zoneless
9. esbuild + Vite: builds que no dan sueño
El build tool de Angular era Webpack. Cumplía su función, pero era lento. En proyectos medianos, una build completa podía tomarse 2-3 minutos. El hot reload funcionaba pero no era instantáneo.
Angular v17 cambió el builder por defecto a esbuild + Vite. Los números hablan solos:
67% más rápido en builds normales
87% más rápido en escenarios con hybrid rendering (SSR)
El dev server ahora sirve ES modules nativos con pre-bundling de dependencias
Source: https://blog.angular.dev/introducing-angular-v17-4d7033312e4b
Para equipos grandes, esto no es trivial. Un equipo de 10 devs que hace builds frecuentes recupera horas de tiempo productivo por semana. Y el feedback loop en desarrollo se siente radicalmente diferente cuando los cambios se reflejan casi al instante.
La migración desde el builder antiguo es automática con ng update. No hay que reconfigurar nada.
10. Angular DevTools: debuggear ya no es adivinar
Angular DevTools es la extensión de Chrome/Firefox que permite inspeccionar aplicaciones Angular en tiempo real. Llegó hace tiempo, pero con v17+ recibió mejoras sustanciales.
El tab de Components muestra el árbol completo de componentes y directivas, permite inspeccionar el estado actual de cada componente (incluyendo signals), y modificar valores en tiempo real para probar comportamientos.
El tab de Profiler es el más útil para performance: muestra exactamente cuándo y por qué se ejecutó la detección de cambios en cada ciclo. Si tienes un componente que se re-renderiza innecesariamente, aquí lo ves.
A partir de v17 también expone el árbol de inyección de dependencias — puedes explorar la jerarquía de injectors y ver exactamente qué instancias de servicios existen en qué nivel. Esto es invaluable cuando debuggeas comportamientos extraños con providers.
La limitación: solo funciona en builds de desarrollo. En producción con optimizaciones activadas, no hay acceso. - Source: https://angular.dev/tools/devtools
El cuadro completo
Cuando juntas todo esto — Signals, standalone components, nueva template syntax, @defer, hydration incremental, inject(), zoneless, y esbuild — el Angular de 2025 es un framework fundamentalmente diferente al de 2021.
Feature | Versión | Estado actual |
|---|---|---|
Standalone Components | v14 (2022) | Default desde v19 |
inject() function | v14 (2022) | Estable |
Signals | v16-v17 (2023) | Estable |
@if/@for/@switch | v17 (2023) | Estable |
@defer | v17 (2023) | Estable |
esbuild + Vite | v17 (2023) | Default |
Non-destructive Hydration | v17 (2023) | Estable |
Signal Inputs/Outputs | v17+ (2023-2024) | Estable |
Incremental Hydration | v19-v20 (2024-2025) | Estable |
Zoneless | v20-v21 (2025) | Default en v21 |
La narrativa de "Angular está muerto" era, en retrospectiva, una foto desactualizada. Lo que pasó entre 2022 y 2025 fue un trabajo metodico de modernización que pocos frameworks han ejecutado tan bien sin romper compatibilidad hacia atrás.
¿Vale la pena revisarlo si lo dejaste hace tiempo? La respuesta corta: sí. La respuesta larga: probablemente te sorprenda cuánto de lo que te molestaba ya no está.
Recursos para seguir
Documentación oficial actualizada: https://angular.dev
Angular blog con release notes: https://blog.angular.dev
Guía de Signals: https://angular.dev/guide/signals
Guía de Hydration: https://angular.dev/guide/hydration
Control Flow templates: https://angular.dev/guide/templates/control-flow
Angular Roadmap: https://angular.dev/roadmap
Keywords: Angular Signals, Angular Renaissance, Angular 17 standalone components, Angular performance 2024, Angular zoneless change detection