Resumen de las novedades de Angular 20

· 20 min · linux

Introducción

El equipo de Angular ha lanzado la versión 20 de este popular framework el día 28 de mayo de 2025. Desde el blog oficial de Angular

angular20-release.png

Highlights

Signals

Angular 20 ha introducido una serie de mejoras en el sistema de Signals, que permiten una reactividad más eficiente y flexible en las aplicaciones Angular. Algunas de las mejoras incluyen:

Effects

Siempre que tengas que ejecutar un método asociado al cambio de un valor de una o más señales, puedes usar el efecto effect para ejecutar un código que dependa de una o más señales. Por ejemplo, si tienes una señal count y quieres ejecutar un efecto cada vez que cambie, puedes hacerlo de la siguiente manera:

import { effect, signal } from '@angular/core'
@Component({
  selector: 'app-signals20',
  imports: [],
  templateUrl: './signals20.html',
  styleUrl: './signals20.css'
})
export class Signals20 implements OnDestroy {
  count: WritableSignal<number> = signal(0)
  // en teoría se autodestruye al salir del componente, pero se puede destruir manualmente en el OnDestroy
  retEffect: EffectRef
  constructor() {
    // Sólo de ejecuta  de manera asíncrona una vez por cambio de señal
    this.retEffect = effect(() => {
      console.log(`The current count is: ${this.count()}`)
    })
  }

  ngOnDestroy(): void {
    this.retEffect.destroy()
  }

  increment() {
    this.count.update((value) => {
      return value + 1
    })
  }
}

Fichero de la plantilla signals20.html:

<div>
    <h2>Signals</h2>
    <h3>{{count()}}</h3>
    <button class="btn btn-success" (click)="increment()">Increment</button>
</div>

Como vemos podemos usar la función effect y asociamos una función que se ejecutará cada vez que cambie el valor de la señal count. En este caso, simplemente imprimimos el valor actual de count en la consola.

Por defecto, el efecto se ejecuta de manera asíncrona, lo que significa que no bloquea la ejecución del código y permite que la aplicación siga funcionando mientras se actualiza el valor de la señal. Además podemos destruir el efecto manualmente en el método ngOnDestroy del componente, aunque en teoría se destruye automáticamente al salir del componente.

linkedSignal

Es un tipo señal parecida a la computed, pero que permite cambiar su valor en el caso de ser necesario. Si se actualiza el valor de la señal original, se actualiza automáticamente el valor de la linkedSignal.

import { Component, linkedSignal, signal, WritableSignal } from '@angular/core'

@Component({
  selector: 'app-linked-signal',
  imports: [],
  templateUrl: './linked-signal.html',
  styleUrl: './linked-signal.css'
})

export class LinkedSignalComponent {
  // señal original
  options: WritableSignal<string[]> = signal(['A', 'B', 'C'])
  // linkedSignal
  linkedSignal: WritableSignal<string> = linkedSignal(() => this.options()[0])

  cambiaLinked() {
    this.linkedSignal.update((value) => {
      return `${value}!`
    })
  }

  cambiaSenal() {
    this.options.set(['a', 'b', 'c', 'd'])
  }
}

Ahora el código de la plantilla linked-signal.html:

<h2>LinkedSignal</h2>
  <h3>options: {{options() | json}}</h3>
  <h3>linkedSignal: {{linkedSignal()}}</h3>
  <button class="btn btn-success" (click)="cambiaLinked()">Cambia LinkedSignal</button>
  <button class="btn btn-success" (click)="cambiaSenal()">Cambia Señal original</button>

toSignal

Para crear señales en base a observables, podemos usar la función toSignal. Esta función permite convertir un observable en una señal, lo que facilita la integración de observables en el nuevo sistema de reactividad de Angular.

import { toSignal } from '@angular/core/rxjs-interop'
import { interval } from 'rxjs'

@Component({
  template: `{{ counter() }}`,
})
export class Ticker {
  counterObservable = interval(1000)
  // Get a `Signal` representing the `counterObservable`'s value.
  counter = toSignal(this.counterObservable, { initialValue: 0 })
}

En este ejemplo, toSignal convierte el observable counterObservable en una señal counter, que se actualiza cada segundo con el valor emitido por el observable. El parámetro initialValue se utiliza para establecer un valor inicial para la señal.

Hidratación incremental

Configuración del modo de renderizado a nivel de ruta (SSR)

Angular 20 ha introducido una nueva forma de configurar el modo de renderizado a nivel de ruta para las aplicaciones que utilizan Server-Side Rendering (SSR). Esta característica permite a los desarrolladores especificar cómo se deben renderizar las rutas en el servidor, lo que puede mejorar el rendimiento y la experiencia del usuario, y la manera de renderizar los componentes dependiendo de cómo se configure.

export const routeConfig: ServerRoute = [
  { path: '/login', mode: RenderMode.Server },
  { path: '/dashboard', mode: RenderMode.Client },
  {
    path: '/product/:id',
    mode: RenderMode.Prerender,
    async getPrerenderParams() {
      const dataService = inject(ProductService)
      const ids = await dataService.getIds() // ["1", "2", "3"]
      // `id` is used in place of `:id` in the route path.
      return ids.map(id => ({ id }))
    }
  }
]

En este ejemplo, se define una configuración de ruta que especifica el modo de renderizado para cada ruta. La ruta /login se renderiza en el servidor, la ruta /dashboard se renderiza en el cliente y la ruta /product/:id se prerenderiza con parámetros específicos.

Mejorada la experiencia de desarrollo

Angular 20 ha mejorado la experiencia de desarrollo con una serie de actualizaciones y mejoras en las herramientas y guías de estilo. Algunas de las mejoras incluyen:

<!-- n on power two -->
{{ n ** 2 }}

<!-- checks if the person object contains the name property -->
{{ name in person }}
<div [class]="`layout col-${colWidth}`"></div>

API’s Experimentales

Se ha metido un API nuevo con la idea de manejar recursos de manera reactiva con señales. La idea es por ejemplo asociar una petición HTTP al cambio de una señal, de manera que cuando cambie el valor de la señal, se realice una nueva petición HTTP. Y que devuelva el dato como otra señal.

const userId: Signal<string> = getUserId()
const userResource = resource({
  params: () => ({ id: userId() }),
  loader: ({ request, abortSignal }): Promise<User> => {
    // fetch cancels any outstanding HTTP requests when the given `AbortSignal`
    // indicates that the request has been aborted.
    return fetch(`users/${request.id}`, { signal: abortSignal })
  },
})

También se podría pedir datos por streaming vía Websocket.

Ayuda a la GenAI

NgIf, NgFor, and NgSwitch obsoletas

A partir de Angular 20, las directivas NgIf, NgFor y NgSwitch se consideran obsoletas. En su lugar, se recomienda utilizar el nuevo control flow, que ofrece una forma más eficiente y flexible de manejar la lógica de control de flujo en las plantillas. Cuando actualizamos las aplicaciones con ng update, se nos ofrece la posibilidad de migrar automáticamente las directivas obsoletas a usar el nuevo control flow. Si quieres ejecutarlo fuera del proceso de update puedes ejecutar el siguiente comando:

ng generate @angular/core:control-flow

Esto significa que para la v22 estarán eliminadas y no se podrán usar, por lo que es recomendable migrar a las nuevas directivas cuanto antes.

Nueva mascota de Angular

El equipo de Angular ha lanzado una serie de propuestas para la nueva mascota oficial de Angular. Estas propuestas incluyen una variedad de diseños y conceptos que reflejan la identidad y los valores de Angular. La comunidad está invitada a participar en la votación para elegir la nueva mascota oficial.

posible-mascota-angular.png

Conclusión

Angular 20 trae una serie de mejoras y nuevas características que mejoran la experiencia de desarrollo y el rendimiento de las aplicaciones Angular. Desde la introducción de nuevas APIs experimentales hasta la mejora de las herramientas de desarrollo, Angular 20 es una actualización significativa que vale la pena explorar.