Novedades de Angular v22: todo lo que cambia en esta versión
Un compañero me preguntó la semana pasada: “¿Merece la pena actualizar ya a Angular v22?”.
Le respondí lo mismo que le diría a ti: las novedades de Angular v22 no son parches ni renombrados. Son APIs nuevas que reemplazan patrones que llevan años instalados en nuestra cabeza — y varias de ellas pasan a ser estables en esta versión. Si llevas tiempo esperando que Angular se parezca a lo que promete ser, v22 es esa versión.
Afecta cómo gestionas estado asíncrono, cómo escribes formularios, cómo arranca la detección de cambios y cómo declaras componentes. Todo a la vez. En una sola versión.
Este post es el mapa. Si quieres ir al detalle de alguna feature concreta, tienes posts específicos linkados donde corresponde.
¿Qué es Angular v22? Angular v22 es la versión que consolida las Signal APIs como estándar principal del framework. Introduce la Resource API estable (
resource(),rxResource()), el nuevo decorator@Service(), Signal Forms experimental, y avanza en zoneless como camino recomendado para proyectos nuevos. Es la versión con más cambios de fondo desde que Angular adoptó standalone components.
Qué cambia de raíz en Angular v22
Antes de ver las APIs una a una, hay una idea central que explica casi todo lo que trae v22:
Angular se está moviendo hacia un modelo completamente basado en Signals, sin Zones y sin boilerplate innecesario.
Eso no es nuevo como dirección. Lo que es nuevo en v22 es que varias piezas de ese puzzle pasan a ser estables o por lo menos usables en producción experimental. Ya no es solo teoría.
Patrón anterior vs equivalente en Angular v22
| Patrón anterior | Equivalente en Angular v22 |
HttpClient.get().subscribe() |
httpResource() (experimental) |
Subject + switchMap |
resource() / rxResource() (estables) |
new FormControl() |
Signal Forms formField() (experimental) |
@Injectable({ providedIn: 'root' }) |
@Service() (estable) |
ChangeDetectionStrategy.Default |
ChangeDetectionStrategy.Eager |
standalone: true explícito |
Default desde v22 — ya no hace falta |
allowSignalWrites: true en effect |
Eliminado — ya no necesario |
Resource API en Angular v22: gestión de estado asíncrono sin subscribe
La novedad más importante de Angular v22 en el día a día son las tres APIs de Resource. resource() y rxResource() pasan a ser estables:
resource() — async state sin subscribe
resource() es la forma nativa de Angular para manejar operaciones asíncronas reactivas. Defines un loader con una Promise y el framework gestiona loading, error y datos por ti.
import { resource, signal } from '@angular/core';
@Component({ ... })
export class ProductListComponent {
categoryId = signal(1);
products = resource({
request: () => this.categoryId(),
loader: ({ request }) =>
fetch(/api/products?category=${request}).then(r => r.json())
});
}
En el template:
@if (products.status() === 'loading') {
<p>Cargando...</p>
}
@if (products.status() === 'resolved') {
@for (product of products.value(); track product.id) {
<li>{{ product.name }}</li>
}
}
Los estados posibles son strings literales: 'idle', 'loading', 'reloading', 'resolved', 'error', 'local'. No hay enum. No uses ResourceStatus.Loading — no existe así.
rxResource() — cuando el backend habla en Observables
Si tienes servicios que devuelven Observables (la mayoría de los proyectos reales), usa rxResource(). La clave es que el parámetro se llama stream, no loader:
import { rxResource } from '@angular/core/rxjs-interop';
@Component({ ... })
export class OrdersComponent {
userId = signal(42);
orders = rxResource({
request: () => this.userId(),
stream: ({ request }) => this.ordersService.getByUser(request)
});
}
La diferencia con resource() es solo el parámetro: loader para Promises, stream para Observables. El resto del comportamiento es idéntico.
httpResource() — HTTP reactivo sin HttpClient.get().pipe(...)
httpResource() es la versión experimental especializada en HTTP — úsala con precaución en producción. El primer argumento siempre es una función, nunca un string directo.
import { httpResource } from '@angular/core';
@Component({ ... })
export class UserProfileComponent {
userId = signal(1);
user = httpResource<User>(() => /api/users/${this.userId()});
}
Requiere provideHttpClient() en tu configuración. Devuelve un HttpResourceRef que expone .value(), .status(), .statusCode() y .headers() como signals.
Si quieres profundidad en las tres APIs, tengo un post dedicado: Resource API en Angular 22: el fin del subscribe() manual.
linkedSignal() estable: el signal derivado que puedes escribir
linkedSignal() resuelve un problema concreto que no tenía solución limpia hasta ahora: quieres un signal que se inicialice (y se resetee) a partir de otro signal, pero que también puedas modificar manualmente.
Ejemplo clásico: una lista de items y un item seleccionado que vuelve al primero cuando cambia la lista.
import { signal, linkedSignal } from '@angular/core';
@Component({ ... })
export class ItemSelectorComponent {
items = signal(['Angular', 'React', 'Vue']);
selectedItem = linkedSignal(() => this.items()[0]);
}
Cuando items cambia, selectedItem vuelve automáticamente al primer elemento. Pero puedes escribir en selectedItem en cualquier momento:
this.selectedItem.set('React'); // funciona
Con un computed() no puedes hacer eso. Con un signal() normal perderías el vínculo con items. linkedSignal() es la pieza que faltaba entre los dos.
debounced() experimental: búsquedas sin setTimeout manual
debounced() es una API experimental de Angular v22 para manejar valores con delay configurable. No devuelve un Signal — devuelve un Resource, así que se lee con .value() y .status(), igual que resource().
Es ideal para barras de búsqueda donde no quieres disparar una petición por cada tecla. Al ser experimental, la firma exacta puede cambiar antes de estabilizarse — consulta siempre la documentación oficial de angular.dev antes de usarla en producción.
Signal Forms experimental: formularios basados en Signals
Angular v22 introduce Signal Forms como API experimental. No reemplaza a Reactive Forms todavía — no está pensado para producción sin asumir el riesgo de breaking changes — pero marca la dirección clara hacia la que van los formularios en Angular.
La premisa es eliminar el FormBuilder, los FormGroup y los valueChanges basados en Observables. Todo como signals.
Es la feature que más puede cambiar antes de estabilizarse, así que úsala con precaución en proyectos reales y estate pendiente a las release notes.
effect() ya no necesita allowSignalWrites
Pequeño pero importante: a partir de v22, escribir en un signal dentro de un effect() está permitido por defecto. La opción allowSignalWrites está deprecada y no debes usarla.
Antes (v21 y anteriores):
// v21 — necesitabas esto:
effect(() => {
this.count.set(this.source() * 2);
}, { allowSignalWrites: true });
Ahora (v22):
// v22 — simplemente funciona:
effect(() => {
this.count.set(this.source() * 2);
});
Si tienes código con allowSignalWrites: true, no se rompe todavía. Pero el compilador te avisará que está deprecated. Es uno de esos cambios que limpias en 10 minutos con un find & replace.
ChangeDetectionStrategy.Eager y el adiós definitivo a Default
Angular v22 introduce ChangeDetectionStrategy.Eager como el nuevo nombre para la estrategia de detección de cambios que antes se llamaba Default.
ChangeDetectionStrategy.Default pasa a ser un alias deprecated de Eager. Si tienes componentes sin estrategia explícita, no se rompen, pero la nomenclatura oficial cambia:
// Antes (sigue funcionando, pero deprecated el nombre):
@Component({
changeDetection: ChangeDetectionStrategy.Default
})
// Ahora (lo correcto en v22):
@Component({
changeDetection: ChangeDetectionStrategy.Eager
})
En la práctica, para componentes nuevos lo relevante sigue siendo usar OnPush cuando sea posible y avanzar hacia zoneless. Eager es el fallback explícito cuando necesitas el comportamiento clásico por nombre, no por omisión.
Zoneless en Angular v22: cómo funciona y cuándo usarlo en producción
Zone.js ha sido la pieza más criticada del runtime de Angular desde que existe. En v22 el modo zoneless avanza significativamente como alternativa estable para proyectos nuevos.
Sin Zone.js, Angular solo ejecuta detección de cambios cuando se lo dices explícitamente: a través de signals, events, o marcando el componente como sucio manualmente. El resultado son aplicaciones más predecibles, más fáciles de depurar y más rápidas en la mayoría de los escenarios.
Para activarlo en una app nueva:
// main.ts
bootstrapApplication(AppComponent, {
providers: [
provideExperimentalZonelessChangeDetection()
]
});
En el Curso de Angular Moderno cubrimos la arquitectura zoneless con Signals desde cero — actualizado a v22.
standalone: true ya no es necesario
A partir de v22, standalone es el default. No necesitas escribir standalone: true en ningún componente nuevo:
// v21 y anteriores — necesitabas declararlo:
@Component({
standalone: true,
selector: 'app-product-card',
...
})
// v22 — standalone por defecto, sin declaración:
@Component({
selector: 'app-product-card',
...
})
Solo necesitas standalone: false si quieres un componente que NO sea standalone — que es el caso raro ahora.
Nuevo decorator @Service() en Angular v22: para qué sirve
Si llevas tiempo usando inject() en lugar de constructor injection, este cambio te va a gustar.
Angular v22 introduce @Service() como alternativa directa a @Injectable({ providedIn: 'root' }). Sin opciones, sin configuración — declaras la clase como servicio y Angular la provee en root automáticamente.
// Antes
@Injectable({ providedIn: 'root' })
export class AuthService {
private http = inject(HttpClient);
}
// Angular v22
@Service()
export class AuthService {
private http = inject(HttpClient);
}
Un detalle importante: @Service() solo funciona con inject(). Si intentas usar constructor injection con @Service(), obtendrás un error — el decorator asume el modelo de inyección funcional. Si necesitas constructor injection o configuración avanzada como providedIn: 'platform', sigue usando @Injectable.
Es coherente con la dirección que lleva el framework desde que inject() llegó — alejarse del constructor como único punto de entrada de dependencias.
Angular v22: tabla completa de features estables y experimentales
| Feature | Estado en v22 | Para producción |
resource() |
Estable | Sí |
rxResource() |
Estable | Sí |
linkedSignal() |
Estable | Sí |
@Service() |
Estable | Sí |
standalone: true default |
Estable | Sí |
allowSignalWrites deprecated |
Estable | Quitar el flag |
ChangeDetectionStrategy.Eager |
Estable | Sí |
httpResource() |
Experimental | Con precaución |
debounced() |
Experimental | Con precaución |
| Signal Forms | Experimental | No recomendado aún |
| Zoneless | Developer preview | Proyectos nuevos |
Cómo migrar a Angular v22 desde Angular 20 o 21: guía paso a paso
No necesitas migrar todo a la vez. Esta es la secuencia que tiene más sentido:
- Elimina
allowSignalWrites: truede tus effects. Es trivial y lo haces en un PR. - Adopta
linkedSignal()donde tengas signals que dependen de otros y se resetean. Los encontrarás fácilmente. - Migra a
@Service()en servicios simples que ya useninject(). La ganancia es inmediata en legibilidad. - Empieza a usar
resource()en componentes nuevos en lugar deswitchMap+HttpClient.get(). No tienes que migrar los existentes de golpe. - Experimenta con zoneless en un proyecto nuevo o en un módulo aislado.
- Deja Signal Forms para más adelante hasta que estabilice.
Si quieres ir más al fondo en cómo funciona el testing de estos nuevos patrones, tengo el Curso de Testing en Angular actualizado con Jest y Testing Library — resource() y linkedSignal() cambian cómo se escriben los tests de componentes.
FAQ — Preguntas frecuentes sobre Angular v22
¿Es Angular v22 compatible con Angular v19 o v20?
La migración de v19/v20 a v22 es incremental. Las APIs nuevas son aditivas — no rompen el código existente. standalone: true sigue funcionando aunque ya no sea necesario. ChangeDetectionStrategy.Default sigue siendo un alias válido aunque deprecated. Puedes actualizar con ng update y adoptar las nuevas APIs a tu ritmo sin necesidad de reescribir nada de golpe.
¿httpResource() reemplaza a HttpClient en Angular v22?
No. HttpClient no desaparece en v22 y sigue siendo la opción recomendada para lógica HTTP compleja. httpResource() es una alternativa más ergonómica para casos concretos: cuando tienes un signal como parámetro reactivo de la petición y quieres gestionar loading/error automáticamente. Para interceptores custom, peticiones en paralelo o manejo avanzado de headers, HttpClient con RxJS sigue siendo la herramienta correcta. Además, httpResource() es experimental en v22, así que no es recomendable adoptarlo masivamente en proyectos en producción todavía.
¿Qué diferencia hay entre resource() y httpResource()?
resource() es genérico: acepta cualquier función que devuelva una Promise como loader. httpResource() está especializado en HTTP y usa internamente HttpClient, por lo que respeta interceptores, el provideHttpClient() configurado y expone metadatos de la respuesta como .statusCode() y .headers(). Para llamadas HTTP simples con parámetros reactivos, httpResource() es más cómodo. Para lógica asíncrona que no sea HTTP, resource() es la opción.
¿Signal Forms reemplaza a Reactive Forms en v22?
No. Signal Forms es experimental en v22 y no está pensado para reemplazar Reactive Forms todavía. Reactive Forms sigue siendo la opción estable y recomendada para formularios complejos en producción. Signal Forms marca la dirección futura del framework — formularios completamente basados en signals sin FormBuilder ni valueChanges — pero antes de considerarla lista para producción necesita que la API se estabilice, cosa que no ocurre en v22.
¿Puedo usar zoneless ya en producción con Angular v22?
Depende del proyecto. Para proyectos nuevos que uses signals de forma consistente, zoneless es viable — el equipo de Angular lo recomienda como el camino a seguir. Para proyectos existentes que mezclan Zone.js con código legacy que depende del ciclo de detección de cambios automático, la migración requiere más cuidado. En v22 el modo zoneless sigue marcado como “experimental” en el nombre del provider (provideExperimentalZonelessChangeDetection()), aunque en la práctica es bastante estable para proyectos nuevos bien estructurados.
¿linkedSignal() es lo mismo que computed() con posibilidad de escritura?
Conceptualmente se parecen, pero con una diferencia clave: linkedSignal() tiene dependencia reactiva sobre otro signal para su valor inicial y para resetearse automáticamente cuando ese signal cambia. computed() es de solo lectura — no puedes escribir en él. signal() es escribible pero no tiene vínculo reactivo con otros signals. linkedSignal() combina los dos comportamientos: se actualiza cuando cambia su fuente y también acepta escrituras manuales, lo que lo hace ideal para estados que tienen un “valor por defecto reactivo” pero que el usuario puede sobrescribir.
¿Para qué sirve @Service() y cuándo no usarlo?
@Service() es el nuevo decorator de Angular v22 que simplifica la declaración de servicios singleton en root. Equivale a @Injectable({ providedIn: 'root' }) pero sin configuración. Solo funciona con inject() — si intentas constructor injection con @Service(), obtendrás un error. Úsalo en servicios simples que ya sigan el patrón de inject(). Si necesitas providedIn: 'platform', providedIn: 'any' u otras opciones avanzadas, sigue usando @Injectable con su configuración completa.
Si estás construyendo con IA en tu día a día como developer, el curso Construye con IA te muestra cómo integrar Claude Code en tu flujo de trabajo real con proyectos Angular y TypeScript.
Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.
