Angular 20: ¿Qué hay de nuevo?

Nuevos requisitos: TypeScript v5.8 y Node v20

Angular v20 no es nada ambiguo con sus nuevos requisitos. Ahora tienes que estar en TypeScript v5.8 (que, por cierto, ya era compatible desde v19.2), así que es hora de decir adiós a las versiones más antiguas de TypeScript).

Y para Node, dale la bienvenida a Node v20 como el nuevo mínimo. Angular v19 fue la gira de despedida para Node 18. ¡Es hora de actualizar esos entornos, colegas!

Guía de estilo 2025: ¡Una bocanada de aire fresco!

¡La Guía de Estilo de Angular ha recibido una renovación importante! Muchas recomendaciones se han reducido para centrarse en lo que realmente importa.

  • Nombres de archivo recortados: Olvídate de UserComponent en user.component.ts. Ahora es simplemente User en user.ts. Lo mismo para directivas, pipes, etc. La CLI ya está aplicando esto para cosas nuevas. ¿Cuánto tardará tu memoria muscular en adaptarse?
  • Estructura de carpetas más ligera: La carpeta app de nivel superior podría desaparecer pronto (aunque la CLI aún no ha llegado a ese punto).
  • Visibilidad de propiedades: protected es ahora la forma para las propiedades que solo se usan en tu plantilla, y readonly para todas esas propiedades inicializadas por Angular (input(), output(), etc.). ¡Tiene sentido!
  • Bindings de Clases y Estilos: ¡Es oficial! [class.something] y [style.something] son los campeones recomendados sobre ngClass y ngStyle.

Esto es un cambio grande. Los nuevos proyectos lo adoptarán por defecto. ¿Los existentes? Bueno, o migras o te quedas con las viejas costumbres (la CLI ayuda con una configuración para eso, ¡uf!).

APIs de Signals: ¡Mayormente Luz Verde y Estables!

¡Las Signals son el futuro, y las APIs se están consolidando! La mayoría ahora son estables:

  • effect()
  • toSignal()
  • toObservable()
  • afterRenderEffect()
  • afterNextRender()
  • linkedSignal()
  • PendingTasks

¡Ojo con esto! afterRender() ha sido renombrado a afterEveryRender() y es estable. Crucialmente, el nombre antiguo desapareció sin migración automática. ¡Uf, eso podría doler!

Además, TestBed.flushEffects() (esa API en developer preview un poco escurridiza) está obsoleta. Usa TestBed.tick() ahora, que ejecuta todo el proceso de sincronización, mucho más cercano al comportamiento real de la aplicación. effect() perdió su opción forceRoot (¿alguien la usaba mucho?), y toSignal() eliminó rejectErrors (¡bien pensado, mejores prácticas al poder!). pendingUntilEvent() todavía se está calentando en developer preview.

Zoneless: ¡Fuera de Experimental, entra en Developer Preview!

Si las signals son el futuro de la reactividad, ¡Zoneless es el futuro de la detección de cambios! Ya no es "experimental", ahora está oficialmente en developer preview.

  • provideExperimentalZonelessChangeDetection ahora es simplemente provideZonelessChangeDetection.
  • El flag de la CLI --experimental-zoneless ahora es simplemente --zoneless.

La CLI incluso te preguntará si quieres habilitarlo para nuevos proyectos. ¿Listos para el #NoZone?

Que se va, que se queda y que puede romper nuestro código:

Como en toda versión mayor, algunas cosas se van a la basura:

  • Las directivas ngIf, ngFor, ngSwitch están oficialmente obsoletas. El control de flujo incorporado (@if, @for, @switch) es el rey ahora. Empieza a migrar; ¡probablemente desaparecerán en v22! ng update te echará una mano.
  • fixture.autoDetectChanges(boolean): El parámetro booleano ha desaparecido. Simplemente usa fixture.autoDetectChanges(). ¿Usar fixture.autoDetectChanges(false) en un test zoneless? Eso ahora lanzará un error.
  • TestBed.get(): ¡Finalmente, finalmente DESAPARECIÓ! Estuvo obsoleto desde allá por Angular v9. TestBed.inject() es tu amigo (desde hace mucho). Una migración automática se encargará de esto.
  • Enum InjectFlags: Eliminado. Los objetos de opciones para las APIs de DI (como inject()) han sido la norma desde v14.1.
  • Token DOCUMENT: Se movió de @angular/common a @angular/core. Una migración actualizará tus imports.
  • @angular/platform-browser-dynamic: Obsoleto en favor de @angular/platform-browser. Tendrás que actualizar manualmente los imports para este por ahora.
  • @angular/platform-server/testing: También obsoleto, sin reemplazo. Las pruebas E2E son ahora la forma recomendada para verificar aplicaciones SSR.
  • Integración con HammerJS: Obsoleta. HammerJS no ha visto una actualización en 8 años. Es hora de decir adiós a esas entidades en el framework.
  • Atributos ng-reflect-*: Desaparecen por defecto en modo desarrollo. Eran para antiguas devtools. Si dependías de ellos (¡probablemente no deberías!), puedes rehabilitarlos con provideNgReflectAttributes(). Pero quizás... ¿mejor no?

Plantillas: ¡Nuevos trucos bajo la manga!

Tus plantillas se han vuelto un poco más expresivas:

  • Exponenciación: {{ 2 ** 3 }} ahora es posible. ¡Por lo que podemos hacer cálculos de manera más fácil!.
  • Tagged Template Literals: Sí, {{ translate\app.title` }}` está aquí (técnicamente desde v19.2, pero ahora está asentado).
  • Operador void: Úsalo como <button (click)="void selectUser()"> para ignorar explícitamente el retorno de una función, especialmente útil para listeners de eventos donde return false podría prevenir el comportamiento por defecto.
  • Operador in: Verifica propiedades como @if ('invoicing' in permissions). ¡Súper útil!

Diagnósticos extendidos: ¡El compilador te mantiene informado!

Más verificaciones incorporadas para atrapar errores comunes:

  • missingStructuralDirective: ¿Usando algo como *ngTemplateOutlet pero olvidaste importar NgTemplateOutlet? El compilador ahora te lo hará saber (con strictTemplates).
  • uninvokedTrackFunction: ¿Escribiste @for (user of users; track getUserId) en lugar de track getUserId(user)? Recibirás un codazo amistoso.
  • unparenthesizedNullishCoalescing: Mezclar ?? con && o || (ej. a ?? b && c) ahora requiere paréntesis para mayor claridad, como {{ a ?? (b && c) }}. TypeScript hace esto, ¡así que es genial verlo también en las plantillas!

Puedes suprimirlos en tsconfig.json si de verdad quieres, pero... ¿Por qué querrías?

Chequeo de tipos en host: ¡Ya no más misterios!

Hay una nueva opción de compilador typeCheckHostBindings (ya en nuevos proyectos CLI). Si usas metadatos de host en decoradores (o @HostBinding/@HostListener), el compilador ahora verifica:

  1. Si el objetivo del binding/listener (ej. value en [value]="value()") es realmente válido para el elemento host.
  2. Si la propiedad en tu clase de componente/directiva (ej. value()) realmente existe.

¡Esto es brutal para pillar typos y asegurar que tus bindings de host sean legítimos!

Manejo de errores: ¡Menos errores que se escapan!

  • provideBrowserGlobalErrorListeners: Un nuevo provider (agregado por defecto en nuevos proyectos CLI) para registrar listeners de errores globales en el navegador. Atrapa errores que Angular podría pasar por alto.
  • Los errores en los listeners de eventos ahora se reportan al manejador de errores interno de Angular. Esto significa que podrías ver errores en tests que antes eran silenciosos. Es hora de arreglarlos (o usar rethrowApplicationErrors: false en configureTestingModule como último recurso).

Componentes creados dinámicamente: ¡Sube de nivel!

¡createComponent() (y ViewContainerRef.createComponent) se volvieron mucho más geniales en v20! Ahora puedes pasar opciones para:

  • Especificar directives a aplicar al componente dinámico.
  • Proveer valores de input usando la nueva función inputBinding().
  • Declarar two-way bindings con twoWayBinding().
  • Escuchar outputs con outputBinding().

Esto es un gran avance respecto a llamar setInput después de la primera detección de cambios. ¡Más poder y control! ¿Llegará esto también a TestBed.createComponent()?

Formularios: Aún esperando las señales, pero...

No hay formularios basados en signals todavía (¡todos estamos al borde del asiento esperando eso!). Pero v20 trae un par de ajustes pequeños pero bienvenidos:

  • userForm.resetForm(undefined, { emitEvent: false }): Resetea formularios sin disparar eventos.
  • markAllAsDirty(): Finalmente, un método en AbstractControl para marcar un control y todos sus descendientes como dirty. ¡A markAllAsTouched() le faltaba un hermano!

Router: ¡Navegación más fluida a la vista!

Algunas mejoras interesantes para el router:

  • Opciones de Scroll: Pasa opciones nativas de scroll a ViewportScroller.scrollToAnchor()/scrollToPosition(). Por ejemplo, behavior: 'smooth' para ese efecto de scroll elegante.
  • Resolvers con más contexto: ¡Los resolvers de rutas hijas ahora pueden acceder a datos resueltos de su ruta padre! route.data.user del padre estará disponible. ¡Less gimnasia para obtener datos!
  • Redirecciones asíncronas: La opción redirectTo en las configuraciones de ruta ahora puede aceptar una función que devuelva una Promesa o un Observable para redirecciones asíncronas. (Técnicamente un breaking change debido a la evolución del tipo de retorno).
  • Soporte para Custom Elements: ¿Escribiendo Web Components? Ahora puedes usar un custom element como host de un RouterLink.

Http: APIs de resource Evolucionando y keepalive!

  • Cambios en API de resource: El parámetro query de resource() ahora es params. Para rxResource(), loader ahora es stream. El método reload se movió a WritableResource (solo los recursos mutables pueden recargarse).
  • Actualizaciones en httpResource: La opción map ahora es parse. Puedes especificar el contexto HTTP en las opciones. Y, la petición debe ser ahora reactiva (ej. httpResource<User[]>(() => '/users') en lugar de solo la URL como string).
  • Soporte keepalive: HttpClient ahora soporta la opción keepalive cuando se usa la API Fetch (habilitada con withFetch()). Las peticiones no se abortarán si la página se descarga. Útil para cosas como analíticas.

Profiling: ¡Mira dónde es afectado el rendimiento de tu app!

Hay una nueva función enableProfiling() en @angular/core. Llama a esto, y Angular usará la API de Performance del navegador para etiquetar operaciones del framework (detección de cambios, plantillas, outputs, defer, etc.). Luego, abre las Devtools de Chrome, graba un perfil de rendimiento y mira la pista personalizada "Angular". ¡Por fin, una vista clara del rendimiento interno!

Devtools: ¡Mejores Perspectivas!

Las Devtools de Angular se están volviendo más inteligentes:

  • Los componentes OnPush ahora se marcan como tales en el árbol de componentes.
  • Los bloques diferidos (defer) también se muestran.
  • El soporte para Signals está mejorando – ¡pronto deberíamos ver el árbol de signals! Echar un vistazo bajo el capó ahora es más fácil.

SSR: ¡APIs estables y configuración optimizada!

Buenas noticias para el Server-Side Rendering:

  • ¡Las APIs withI18nSupport() y withIncrementalHydration() ahora son estables!
  • provideServerRendering() (ahora en @angular/ssr en lugar de @angular/platform-server) se combina con provideServerRoutesConfig() en una única función provideServerRendering(withRoutes(serverRoutes)). Una migración se encargará de esto.
  • Las nuevas apps CLI con --ssr obtienen Express v5 y soporte de enrutamiento en servidor por defecto (la opción --server-routing ha desaparecido).

Angular CLI: ¡Esto es ENORME!

La CLI vio una tonelada de cambios. Prepárense:

  • Nomenclatura actualizada para Guía de estilo 2025: Como se mencionó, user.ts (con clase User) en lugar de user.component.ts (clase UserComponent). Igual para directivas, servicios. Pipes, resolvers, etc. usan guiones en los nombres de archivo (from-now-pipe.ts). Una migración de angular.json configura tus proyectos existentes para usar la convención antigua si quieres una transición suave
  • Configuración de TypeScript: La opción module ahora es preserve (refleja mejor los bundlers modernos). tsconfig.json usa un estilo de "solución", referenciando tsconfig.app.json y tsconfig.spec.json.
  • angular.json simplificado: Los nuevos proyectos usan @angular/build directamente, deshaciéndose de @angular-devkit/build-angular y sus dependencias transitivas de Webpack. ¡Eso es casi 200 Mb menos en node_modules! Algunas opciones como outputPath también se eliminan ya que tienen valores por defecto sensatos. ¡Más ligero y potente!
  • Configuración de Browserslist: Ahora apunta a la base "ampliamente disponible" (navegadores lanzados hace < 30 meses del conjunto principal de Baseline). Soporte de navegadores más consistente y realista.
  • Importadores de paquetes Sass: Ahora puedes usar importadores pkg:, como @use 'pkg:@angular/material' as mat;.
  • Testing Ahead of Time (AoT) y Cobertura de Código para Plantillas: Añade "aot": true a tus opciones de test en angular.json. ¡Ejecuta tests en el mismo modo que producción! Además, obtienes cobertura de código para las plantillas.
  • Testing con Vitest (¡Experimental!): ¡Notición! La CLI ahora soporta ejecutar tests con Vitest. Hay un nuevo builder @angular/build:unit-test. Karma y Jasmine podrían tener un nuevo retador.
    • Configúralo con "runner": "vitest". Necesitarás un "buildTarget".
    • Por defecto, usa Node con jsdom (instálalo).
    • Actualiza los types de tsconfig.spec.json a ["vitest/globals"].
    • Modo Navegador: ¡Sí! "browsers": ["chromium"] (o firefox, webkit). Usa Playwright o WebdriverIO (instala uno).
    • El modo watch es más rápido (solo ejecuta tests afectados). Las ejecuciones completas podrían ser un poco más lentas que Karma.
    • --no-watch a menudo es implícito en CI.
    • Nuevo flag --debug (Vitest + jsdom/Playwright).
    • Limitaciones: Aún no hay archivo de configuración de Vitest personalizado. Pero la opción providersFile te permite configurar cosas como testing zoneless. Reporter y exclusiones de cobertura están en angular.json.
  • Carpetas de Workspace automáticas en Chrome: ¡El servidor de desarrollo Vite ahora ayuda a las Devtools de Chrome a mapear los archivos de tu proyecto, permitiendo edición directa desde Devtools que se guarda en tu disco! Habilítalo en los flags de Chrome.
  • Sourcemaps sin fuentes: Genera sourcemaps sin incrustar el código fuente original ("sourcesContent": false). Genial para reportar errores en producción sin exponer todo tu código.

¡Angular y su futuro junto a la IA!

Angular se posiciona para facilitar el desarrollo de aplicaciones con capacidades de IA generativa:

Archivo: llms.txt Se mantiene un archivo llms.txten el repositorio de Angular. Este archivo ayuda a los grandes modelos de lenguaje (LLMs) a descubrir la documentación y los ejemplos de código más recientes y correctos de Angular, para que generen código más moderno y preciso, evitando problemas con APIs o sintaxis obsoletas.

Guías y recursos: Se están proporcionando guías y recursos, angular.dev/ai incluyendo ejemplos y live streams que muestran cómo integrar Angular con herramientas como Genkit y Vertex AI de Google Cloud.

Qué podríamos llegar a ver en Angular v21

  • Componentes sin selector: Imagina esto:
    import { User } from './user/user'; // El import de TS aún es necesario @Component({ template: '<User [name]="name()" (selected)="selectUser()" />', // ¡NO se necesita el array 'imports' de Angular para User! }) export class App { /* ... */ }
    ¡Componentes usados por su nombre de clase directamente en las plantillas! Las directivas podrían usar un prefijo @ (ej. <User @CdkDrag />). Los pipes también por nombre de clase ({{ date | FromNowPipe }}). La sintaxis está LEJOS de ser final, pero el trabajo en el compilador ha comenzado. ¿Alucinante, no? ¡Esto podría ser revolucionario! Debería llegar un RFC.
  • Formularios con Signals (¿La Tercera Vía?): ¡Prepárense para una posible nueva forma de hacer formularios, separada de template-driven y reactive!
    // PROTOTIPO MUY TEMPRANO - ¡NO USAR AÚN! @Component({ selector: "user-form", imports: [FieldDirective], // Nueva directiva template: ` <form> <label>Username: <input [field]="userForm.username" /></label> <label>Name: <input [field]="userForm.name" /></label> </form> `, }) class UserFormComponent { userModel = signal<UserModel>({ /* ... */ }); protected readonly userForm: Field<User> = form<User>( // nueva función form() userModel, // datos a editar (userPath: FieldPath<User>) => { // esquema para comportamiento dinámico y validación disabled(userPath.username, () => true, "No se puede cambiar el nombre de usuario"); required(userPath.name); error(userPath.age, ({ value }) => value() < 18, "Debe ser mayor de 18"); } ); }
    Una nueva función form() y clase Field para manejar el estado del formulario con signals. ¡Echa un vistazo al design doc si tienes curiosidad, o espera al RFC!