¿Qué versión elegir? Qué no tendrás en la LTS
El curso usa Angular 22.2, la versión con soporte activo. La última LTS es Angular 21, con soporte hasta junio de 2027.
Si eliges la LTS para tu proyecto o tu grupo, casi todo el curso te sirve igual, pero hay características que no tendrás o que funcionan de otra manera. Esta página reúne todos esos apartados, con lo que tienes que hacer en su lugar.
| Marca | Significado |
|---|---|
| 🆕 | No existe en la LTS (Angular 21) |
| 🔄 | Existe en la LTS, pero cambia: estado, valor por defecto o comportamiento |
⚠️ Importante: Algunas diferencias no dan ningún error al compilar, como OnPush por defecto o la herencia de parámetros del Router. Revísalas aunque tu código compile.
Resumen
El curso tiene 177 apartados que dependen de la versión, en 30 módulos.
| Versión | 🆕 Nuevo | 🔄 Cambia |
|---|---|---|
| 22.0 | 56 | 93 |
| 22.1 | 7 | 2 |
| 22.2 | 17 | 2 |
Detalle por módulo
00 - Introducción a Angular
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
El servicio Departamentos con @Service | 🆕 22.0 | No disponible | Importa Injectable y escribe @Injectable({ providedIn: 'root' }) en lugar de @Service(); el resto del ejemplo compila igual. |
El componente, sin changeDetection | 🔄 22.0 | Un componente sin changeDetection usa OnPush; en la LTS (Angular 21) el valor por defecto es Default, que Angular 21.2 también llama Eager. | Añade changeDetection: ChangeDetectionStrategy.OnPush a cada componente para tener el mismo comportamiento. |
Pedir los datos a la API con httpResource | 🔄 22.0 | httpResource pasa a ser estable; en la LTS (Angular 21) existe, pero es experimental y puede cambiar sin aviso. | Este ejemplo compila igual, solo que asumes ese riesgo; si prefieres una API estable, usa HttpClient con toSignal, como se explica en el módulo 15. |
| Cómo leer las marcas de versión | 🆕 22.0 | No disponible | Importa Injectable y escribe @Injectable({ providedIn: 'root' }) en lugar de @Service(); el resto del ejemplo compila igual. |
01 - Entorno y Angular CLI
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
| Versiones admitidas | 🔄 22.0 | Angular 22 exige Node ^22.22.3, ^24.15.0 o >=26.0.0; la LTS (Angular 21) admite ^20.19.0, ^22.12.0 o ^24.0.0. | Node 24 sirve en ambas, así que instala una versión 24.15 o posterior y podrás trabajar con las dos sin cambiar nada. |
Componentes sin changeDetection | 🔄 22.0 | Un componente sin changeDetection usa la estrategia OnPush. En la LTS (Angular 21) el valor por defecto es Eager, y el CLI tampoco escribe esa propiedad. | Añade changeDetection: ChangeDetectionStrategy.OnPush a cada componente para tener el mismo comportamiento. |
strict y TypeScript 6 | 🔄 22.0 | Angular 22 usa TypeScript 6, donde strict está activado por defecto, y por eso ng new ya no escribe esa línea en tsconfig.json. La LTS (Angular 21) usa TypeScript 5.9, cuyo valor por defecto es false. | Mantén "strict": true, que ng new escribe en su tsconfig.json, y tendrás las mismas comprobaciones. |
strictTemplates | 🔄 22.0 | Las plantillas se comprueban en modo estricto aunque tsconfig.json no lo diga, y ng new ya no escribe strictTemplates. En la LTS (Angular 21) el valor por defecto sigue siendo false. | Conserva "strictTemplates": true en angularCompilerOptions, que ng new escribe por ti; si lo quitas, los errores de tipos en plantillas dejan de detectarse. |
Servicios: ng g service genera @Service | 🆕 22.0 | ng g service genera una clase con el decorador @Service(), que no existe en la LTS (Angular 21). | La misma orden genera @Injectable({ providedIn: 'root' }), que hace lo mismo; no tienes que cambiar nada. |
Generar la configuración con ng g ai-config | 🔄 22.0 | Los valores de --tool son claude-code, cursor, gemini-cli, open-ai-codex y vscode, y además de las instrucciones se crea la configuración MCP. En la LTS (Angular 21) los valores son claude, copilot, cursor, gemini, jetbrains, windsurf y agents. | Usa esos nombres en --tool y crea a mano el fichero MCP de tu editor, como se ha mostrado arriba. |
| Herramientas que ofrece | 🔄 22.0 | run_target y las herramientas devserver_* funcionan sin activar nada desde las correcciones de la 22.0. En la LTS (Angular 21) existe además find_examples, y ejecutar órdenes es experimental, con otros nombres (build, test, devserver.start…). | Actívalas con "args": ["-y", "@angular/cli@21", "mcp", "--experimental-tool", "all"]; sin esa opción, el servidor solo consulta información y no ejecuta nada. |
Limitar las carpetas con --root | 🆕 22.2 | La opción --root del servidor MCP no existe en la LTS (Angular 21). | No hay opción equivalente; usa --read-only para impedir que el asistente modifique nada y abre en el editor solo la carpeta del proyecto. |
02 - TypeScript para Angular
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
| 1.3 Qué versión de TypeScript usa cada Angular | 🔄 22.0 | Angular 22 exige TypeScript 6.0, y la LTS (Angular 21) usa TypeScript 5.9. | Instala typescript@~5.9, que es lo que deja ng new en ese proyecto. Todo lo que enseña este módulo compila igual en 5.9, salvo lo marcado expresamente, porque TypeScript 6.0 cambia sobre todo valores por defecto y no la sintaxis. |
| 7.5 Qué puede ver una plantilla | 🆕 22.2 | : una plantilla que lee un miembro private falla con TS2341. | Declara protected lo que use la plantilla, una forma que también es válida en 22.2 y que es la que seguimos en el curso. |
8.8 @Service | 🆕 22.0 | No disponible | Escribe @Injectable({ providedIn: 'root' }), que hace lo mismo. |
10.2 strict por defecto | 🔄 22.0 | Angular 22 usa TypeScript 6, donde strict está activo sin escribirlo, y por eso ng new ya no lo incluye en el tsconfig.json. En la LTS (Angular 21, TypeScript 5.9) el valor por defecto es false. | Mantén "strict": true en compilerOptions, que ya lo escribe ng new, y tendrás las mismas comprobaciones. |
10.4 strictTemplates | 🔄 22.0 | strictTemplates queda activo sin escribirlo, y ng new ya no lo incluye en el tsconfig.json. | Déjalo en "angularCompilerOptions" como "strictTemplates": true, que es lo que escribe ng new en Angular 21, y tendrás la misma comprobación. |
| 10.6 Cambios por defecto en TypeScript 6 | 🔄 22.0 | TypeScript 6 altera varios valores por defecto y marca como obsoletas opciones antiguas. | Con TypeScript 5.9 estas opciones siguen sin dar error, pero conviene hacer el mismo cambio: quitar baseUrl y escribir types y strict de forma explícita te ahorrará trabajo al actualizar. |
03 - Componentes
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
Un componente sin changeDetection usa OnPush | 🔄 22.0 | En la LTS (Angular 21) un componente sin changeDetection usa la estrategia Default, equivalente a Eager. | Añade changeDetection: ChangeDetectionStrategy.OnPush a cada componente para tener el mismo comportamiento, que es lo que recomienda este curso. |
ComponentFactoryResolver ya no existe | 🔄 22.0 | Se han eliminado ComponentFactory, ComponentFactoryResolver y ModuleWithComponentFactories. En la LTS (Angular 21) todavía existen, pero están obsoletos. | No los uses en código nuevo y pasa la clase directamente a viewContainerRef.createComponent(MiComponente), que funciona igual en las dos versiones. |
04 - Plantillas
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
Resultado de ?. | 🔄 22.0 | En la LTS (Angular 21), a?.b en una plantilla da null cuando a es nulo; en 22 da undefined, igual que en TypeScript. | No compares con === undefined; escribe ?? 'valor' o == null, que funcionan igual en las dos versiones. |
Métodos frente a computed | 🔄 22.0 | Un componente sin changeDetection usa OnPush; en la LTS (Angular 21) el valor por defecto es Eager, y en la compilación de desarrollo cada método de plantilla se ejecuta dos veces por revisión. | No tienes que cambiar nada: en producción el recuento es el mismo, y computed se comporta igual en las dos versiones. |
| Comentarios entre atributos | 🆕 22.0 | No disponible | Borra temporalmente el atributo, o mueve la explicación a un comentario <!-- --> antes de la etiqueta; un // entre atributos da el error NG5002: Opening tag "button" not terminated. |
| 15. Miembros privados en la plantilla | 🆕 22.2 | , ni en 22.0 ni en 22.1. | Declara protected todo miembro que lea la plantilla; con private obtendrás el error TS2341. |
05 - Señales
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
La opción set | 🆕 22.1 | : el compilador da el error TS2353. | Usa un computed y un método propio que escriba en la señal original, como se muestra al final de este apartado. |
10. debounced: retardar una señal | 🆕 22.0 | , y en 22 sigue siendo experimental: puede cambiar. | Usa la función conRetardo de esta sección, hecha con effect y setTimeout, o toObservable con debounceTime (módulo 15). Solo pierdes el estado status(). |
| Ejercicio 9: Precio con IVA | 🆕 22.1 | : usa la opción set de linkedSignal. | Devuelve un computed con el precio y una función que escriba en la señal del neto, como en la solución. |
| Ejercicio 10: Buscador con espera variable | 🆕 22.0 | , y sigue siendo experimental: usa debounced. | Usa conRetardo con un tiempo fijo y calcula el estado comparando el texto con su versión retardada, como en la solución. |
06 - Comunicación entre componentes
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
| 4. Objetos en entradas: no los mutes | 🔄 22.0 | En la LTS (Angular 21) un componente sin changeDetection se comprueba siempre que se revisa su padre, así que un objeto mutado llega a verse en el hijo; en 22 usa OnPush y no se ve. | Añade changeDetection: ChangeDetectionStrategy.OnPush a cada componente y no mutes nunca los objetos recibidos: tendrás el mismo comportamiento que aquí. |
07 - Directivas y pipes
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
| La misma directiva por dos caminos | 🆕 22.0 | : allí, una directiva que llega dos veces al mismo elemento da en ejecución NG0309: Directive matches multiple times on the same element. | Asegúrate de que cada directiva llega por un solo camino, por ejemplo exponiendo sus entradas en una directiva agrupadora y no volviendo a aplicarla. |
| Pipes impuros y OnPush por defecto | 🔄 22.0 | En la LTS (Angular 21) un componente sin changeDetection usa Eager, y un pipe impuro se ejecuta en cada ciclo de la aplicación aunque la vista no haya cambiado. En 22 el defecto es OnPush y solo se ejecuta cuando esa vista se marca para revisar. | Añade changeDetection: ChangeDetectionStrategy.OnPush para tener el mismo número de ejecuciones. |
| Dos componentes en un elemento | 🔄 22.0 | En la LTS (Angular 21) dos componentes sobre el mismo elemento compilan, y fallan al ejecutar con NG0300: Multiple components match node. En 22 falla al compilar con NG8023. | El problema es el mismo, pero no lo verás hasta que se dibuje ese elemento, así que tiene que cubrirlo una prueba. |
08 - Servicios e inyección de dependencias
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
| Todo el módulo | 🆕 22.0 | Los ejemplos de este módulo usan el decorador @Service. | Escribe @Injectable({ providedIn: 'root' }) donde veas @Service() y @Injectable() donde veas @Service({ autoProvided: false }). Con ese cambio, todo el código del módulo compila y pasa sus pruebas en Angular 21.2, salvo lo marcado expresamente en cada apartado. |
| Generar un servicio con la CLI | 🔄 22.0 | En la LTS (Angular 21) ng generate service crea una clase con @Injectable({ providedIn: 'root' }). | No tienes que hacer nada: el mismo comando te da la versión con @Injectable, que funciona igual. |
| Estado compartido: siempre en señales | 🔄 22.0 | En la LTS (Angular 21) el valor por defecto es Eager, y este ejemplo muestra Valor: 3. | Guarda igualmente el estado en señales: funciona en las dos versiones y tu código no se romperá al actualizar. |
3. Servicios con @Service | 🆕 22.0 | No disponible | Usa @Injectable({ providedIn: 'root' }) en lugar de @Service(), @Injectable() en lugar de @Service({ autoProvided: false }) y @Injectable({ providedIn: 'root', useFactory: ... }) en lugar de @Service({ factory: ... }). |
De @Injectable a @Service | 🆕 22.1 | No disponible | No la necesitas: tu código con @Injectable seguirá funcionando igual cuando actualices a Angular 22. |
| Liberar los inyectores de ruta | 🆕 22.2 | No disponible | Usa withExperimentalAutoCleanupInjectors(), experimental desde 21.1, que en nuestra prueba se comportó igual. |
14. Carga diferida con injectAsync | 🆕 22.0 | No disponible | Haz const { Servicio } = await import('./servicio') y después injector.get(Servicio), con injector = inject(Injector). Para precargar, llama a import() dentro de requestIdleCallback. |
Precarga con onIdle | 🆕 22.0 | No disponible | Llama a import('./servicio') dentro de requestIdleCallback, desde afterNextRender, como en el ejemplo anterior. |
Cambiar qué significa «reposo» con provideIdleServiceWith | 🆕 22.0 | No disponible | No hay alternativa: Angular 21 no ofrece ninguna API pública para cambiar este comportamiento, así que escribe tu propia lógica de espera antes de llamar a import(). |
| Ejercicio 6: Carga diferida con reintentos | 🆕 22.0 | No disponible | Aplica conReintentos directamente a import(): await conReintentos(() => import('./exportador-csv'))() y luego injector.get(...). |
09 - Detección de cambios
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
| OnPush por defecto | 🔄 22.0 | En la LTS (Angular 21) un componente sin changeDetection usa Default, que equivale a Eager. | Añade changeDetection: ChangeDetectionStrategy.OnPush a cada componente para tener el mismo comportamiento, o ng generate component nombre --change-detection=OnPush al crearlo. |
ng generate y la estrategia | 🔄 22.0 | En la LTS (Angular 21) la opción --change-detection admite Default y OnPush, y el valor por defecto es Default. | Usa --change-detection=OnPush para generar cada componente OnPush; el valor Eager no lo acepta el CLI de la 21. |
Qué hace ng update | 🔄 22.0 | Esta migración solo existe al actualizar a 22. | No te afecta; tus componentes ya son Default, es decir, Eager, y puedes pasarlos a OnPush uno a uno cuando quieras. |
| La misma prueba en la LTS | 🔄 22.0 | En la LTS (Angular 21) todo componente sin changeDetection es Eager, así que el clic en Padre revisa las seis vistas. | Para obtener la tabla anterior, escribe changeDetection: ChangeDetectionStrategy.OnPush en HijoOnPush, HijoSinIndicar y HijoSenal. |
OnPush oculta NG0100 | 🔄 22.0 | En la LTS (Angular 21) un componente sin changeDetection lanza NG0100 en esta situación. En Angular 22 el mismo código falla en silencio, porque es OnPush. | Con los componentes y la raíz en Default sigues viendo el error. Si los pasas a OnPush, añade provideCheckNoChangesConfig({ exhaustive: true }), que existe igual en 21. |
10 - Router
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
Mostrar otra dirección con browserUrl | 🆕 22.0 | No disponible | La entrada browserUrl de RouterLink no está, pero sí la opción equivalente al navegar desde código: router.navigateByUrl('/v', { browserUrl: '/oculta' }). |
[routerLinkActive] con null | 🔄 22.1 | En la LTS (Angular 21) la entrada solo admite string o string[], y pasarle null o undefined no compila. | Convierte el valor antes: [routerLinkActive]="clase() ?? ''". |
Comparar dos URL con containsTree | 🆕 22.2 | No disponible | No hay equivalente público para comparar dos URL cualesquiera. Si una de ellas es la actual, usa isActive(url, router, opciones), que sí existe en la 21. |
Opciones de withComponentInputBinding | 🆕 22.0 | No disponible | withComponentInputBinding() no admite argumentos. Conserva los valores por defecto con transform, y evita que un parámetro de consulta llegue a un input poniéndole a este otro nombre. |
| Herencia de parámetros | 🔄 22.0 | En la LTS (Angular 21) paramsInheritanceStrategy vale 'emptyOnly': una ruta hija con path propio no ve los parámetros del padre. | Añade withRouterConfig({ paramsInheritanceStrategy: 'always' }) a provideRouter para tener el mismo comportamiento. |
11 - Router avanzado
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
Lanzar RedirectCommand | 🆕 22.2 | Allí, un RedirectCommand lanzado con throw acaba en NavigationError y no redirige. | Devuélvelo con return, o añade withNavigationErrorHandler(({ error }) => (error instanceof RedirectCommand ? error : undefined)), que da el mismo resultado. |
containsTree | 🆕 22.2 | No disponible | No hay función pública para comparar dos UrlTree cualesquiera. Para comparar con la URL actual usa isActive(url, router, opciones), que devuelve una señal. |
paramsInheritanceStrategy: la hija ve lo del padre | 🔄 22.0 | En la LTS (Angular 21) el valor por defecto es 'emptyOnly', y una hija con ruta propia no hereda los parámetros del padre. | Añade withRouterConfig({ paramsInheritanceStrategy: 'always' }) para tener el mismo comportamiento. |
11. Limpiar los inyectores de ruta: withAutoCleanupInjectors | 🆕 22.2 | No disponible | Usa withExperimentalAutoCleanupInjectors(), disponible desde 21.1, que en nuestra prueba se comporta igual, aunque es experimental. En Angular 22 ese nombre está obsoleto. |
12. Datos en la ruta: withRouterResources | 🆕 22.2 | En 22.2 está en developer preview. | Para datos que bloquean la navegación usa un resolver (sección 4). Para datos que no la bloquean, usa httpResource en el componente, con la señal de entrada del parámetro; compila en 21, aunque allí es experimental. |
12 - HttpClient
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
fetch por defecto | 🔄 22.0 | En la LTS (Angular 21) el backend por defecto es XHR. | No tienes que hacer nada para seguir este módulo; si quieres fetch, añade withFetch() a provideHttpClient. |
withXhr() | 🆕 22.0 | No disponible | No lo necesitas, porque XHR ya es el backend por defecto. Quita withXhr() y el resto del código funciona igual. |
withFetch() obsoleto | 🔄 22.0 | En la LTS (Angular 21) withFetch() no está obsoleto, y es la forma de activar fetch. | Mantenlo si quieres fetch, y bórralo al pasar a Angular 22, donde ya no hace falta. |
| Un servicio por recurso | 🆕 22.0 | @Service() no existe en la LTS (Angular 21). | En todos los servicios de este módulo, escribe @Injectable({ providedIn: 'root' }) en lugar de @Service() e importa Injectable de @angular/core. El resto del código es igual. |
Qué hay dentro de err.error | 🔄 22.0 | Con el backend fetch por defecto, err.error trae objetos distintos a los de la LTS (Angular 21) en los fallos de red y de formato. | No compares con TypeError ni con SyntaxError; basa la lógica en err.status y en err.error?.name === 'TimeoutError', que dan lo mismo en los dos backends, como hace mensajeDeError. |
| Opciones de progreso | 🆕 22.0 | reportUploadProgress y reportDownloadProgress no existen en la LTS (Angular 21), y reportProgress queda obsoleta. | Escribe reportProgress: true, que activa los dos progresos a la vez con el backend XHR por defecto. |
| Subir con progreso exige XHR | 🔄 22.0 | Como el defecto pasa a ser fetch, para ver el progreso de una subida hay que pedir XHR de forma explícita. | No añadas providers a la ruta, porque el defecto ya es XHR; deja la ruta solo con su loadComponent. |
| 14. JSONP, obsoleto | 🔄 22.1 | withJsonpSupport, HttpClient.jsonp, JsonpInterceptor y JsonpClientBackend pasan a estar obsoletos. En la LTS (Angular 21) no lo están. | Tampoco lo uses en código nuevo; sustitúyelo por peticiones normales a un servidor con CORS o por un proxy propio. |
13 - Resources: datos asíncronos con señales
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
| Todo el módulo | 🔄 22.0 | resource, httpResource y rxResource son estables desde esta versión. En la LTS (Angular 21) existen, pero son experimentales y pueden cambiar sin previo aviso. | Casi todo este módulo funciona igual; los apartados que cambian llevan su propia marca. |
2. resource: params y loader | 🔄 22.0 | resource pasa de experimental a estable. | Se usa igual, importado también desde @angular/core, pero al ser experimental puede cambiar entre versiones; revisa las notas de cada actualización. |
| Cuándo se repite la carga | 🔄 22.2 | Si params se recalcula y da el mismo valor, el recurso no vuelve a cargar (corrección incluida en 22.2.4). En la LTS (Angular 21) sí recarga, porque basta con que cambie una señal leída en params. | Calcula el parámetro en un computed aparte y pásalo como params: el computed no avisa si su resultado no cambia. |
Excepciones en params | 🔄 22.0 | Si params lanza una excepción, el recurso pasa a error y puedes tratarla en la plantilla. En la LTS (Angular 21) la excepción no se captura en el recurso y llega como error sin tratar. | Evita lanzar en params; comprueba los datos y devuelve undefined si no son válidos. |
5. httpResource: recursos sobre HttpClient | 🔄 22.0 | httpResource pasa de experimental a estable. | Se usa igual, importado desde @angular/common/http, pero es experimental. Por debajo usa XMLHttpRequest en lugar de fetch, salvo que configures provideHttpClient(withFetch()). |
6. rxResource: cuando la carga es un Observable | 🔄 22.0 | rxResource pasa de experimental a estable. | Se usa igual, importado desde @angular/core/rxjs-interop, pero es experimental y su opción se llama igualmente stream. |
| 7. Encadenar recursos | 🆕 22.0 | No disponible | Lee el recurso padre desde params con hasValue() dentro de un computed, como en CadenaLts; el hijo queda en idle mientras no haya valor, y los errores del padre no se propagan. |
Caché en SSR con id | 🆕 22.0 | No disponible | resource y rxResource no tienen esta opción; httpResource sí aprovecha la caché de transferencia de HttpClient, así que úsalo para las cargas HTTP de páginas renderizadas en el servidor. |
snapshot | 🔄 22.0 | snapshot y el tipo ResourceSnapshot pasan de experimentales a estables. | Existen desde Angular 21.2 y se usan igual, pero son experimentales; en 21.0 y 21.1 no están disponibles. |
Retrasar búsquedas con debounced | 🆕 22.0 | , y en 22 sigue siendo experimental. | Espera dentro del loader con una promesa ligada a abortSignal, como en BuscadorLts, o usa debounceTime de RxJS (módulo 15). |
14 - RxJS fundamental
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
| Cancelar de verdad: qué pasa con la petición | 🔄 22.0 | Darse de baja de HttpClient aborta la petición en las dos versiones, pero en la LTS (Angular 21) el transporte por defecto es XHR y cancela con xhr.abort(), mientras que en 22 es fetch. | No tienes que cambiar nada en tu código; en la pestaña Red verás el tipo xhr en lugar de fetch. |
15 - RxJS y señales
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
| Un campo normal no se pinta solo | 🔄 22.0 | En la LTS (Angular 21) el valor por defecto es Eager, y un campo normal modificado en un subscribe se pinta en la siguiente pasada que provoque otra cosa; en la 22, con OnPush por defecto, no se pinta. | Usa también toSignal o una señal, y no dependas de esa pasada ajena. |
6. rxResource: un Observable como cargador | 🔄 22.0 | En la LTS (Angular 21) rxResource existe con la misma forma (params, stream), pero es experimental y su API puede cambiar; en la 22 es estable. | El código de esta sección compila y funciona igual; asume el riesgo o usa toSignal con toObservable y switchMap, como en la sección 3. |
Variante sin RxJS: debounced | 🆕 22.0 | , y en la 22 debounced sigue siendo experimental. | Estabiliza el texto con toSignal(toObservable(texto).pipe(debounceTime(300), distinctUntilChanged()), { initialValue: '' }), como en la variante con rxResource de arriba. |
16 - Formularios por plantilla
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
El aviso NG01354 | 🆕 22.1 | : el campo del hijo se queda fuera del formulario sin ningún aviso. | Comprueba con {{ f.value \| json }} que aparecen todos los campos; el arreglo con viewProviders es el mismo. |
| Un hijo no ve los cambios de un control | 🔄 22.0 | En la LTS (Angular 21) un componente sin changeDetection se comprueba en cada pasada, así que un hijo que recibe el NgModel entero sí se repinta. | Ese código funciona, pero pasa igualmente [errores]="control.errors" para que siga funcionando cuando actualices a 22. |
Quitar controles y f.value en pantalla | 🔄 22.0 | En la LTS (Angular 21), en modo desarrollo, mostrar {{ f.value \| json }} mientras se quita un control puede lanzar además NG0100 en la consola; en 22.2 el valor solo se queda atrás, sin aviso. | No te preocupes por ese error si solo es una ayuda de depuración, y usa f.value al enviar, que siempre está al día. |
15. ngModel en controles propios con señales | 🆕 22.0 | : el mismo código falla al ejecutarse con NG01203: No value accessor for form control name. | Implementa ControlValueAccessor en tu componente, como se explica en el módulo 17. |
| Signal Forms como tercera opción | 🔄 22.0 | En la LTS (Angular 21) Signal Forms existe pero es experimental y su API puede cambiar. | Para un proyecto nuevo, prefiere formularios por plantilla o reactivos, que son estables en las dos versiones. |
17 - Formularios reactivos
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
| El componente de mensajes, bien hecho | 🔄 22.0 | Los componentes son OnPush por defecto, y un hijo que lee un AbstractControl por input() no se refresca, porque la referencia del control no cambia. En la LTS (Angular 21) el valor por defecto es Eager y el hijo sí se refresca con el padre. | El código de abajo funciona igual, y es la opción recomendada, porque no depende de la estrategia del componente. |
Controles con señales, sin ControlValueAccessor | 🆕 22.0 | Las directivas formControl y formControlName aceptan un componente con value = model(), sin ControlValueAccessor, y ngNoCva desactiva el adaptador de un elemento. No existe en la LTS (Angular 21), que lanza NG01203: No value accessor. | Implementa ControlValueAccessor, como en el apartado anterior. |
Servicios de sesión y de empleados con @Service | 🆕 22.0 | @Service() no existe en la LTS (Angular 21). | Sustituye @Service() por @Injectable({ providedIn: 'root' }) en estos dos servicios; hacen lo mismo. |
18 - Signal Forms
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
| Todo el módulo | 🔄 22.0 | Signal Forms pasa de experimental a estable. En la LTS (Angular 21.2) existe bajo el mismo paquete y con la misma idea, pero es experimental y algunas funciones cambian de forma o no existen. | Casi todo el módulo compila igual; cada apartado distinto lleva su marca, y la sección 21 reúne todas las diferencias comprobadas con Angular 21.2.25. |
3.4. isFieldTree | 🆕 22.1 | No disponible | Evita la comprobación dinámica accediendo a los campos por su nombre, o conviértelos con as FieldTree<unknown> sabiendo que TypeScript no lo verifica. |
4.3. Números en <input type="text"> | 🆕 22.0 | : <input [formField]> con un number \| null da TS2322. | Usa type="number", o un control propio con transformedValue como el de la sección 17. No fuerces el tipo con $any: en 21 el modelo recibiría el texto "9" en lugar del número. |
5.3. min y max solo con números | 🔄 22.0 | min y max dejan de admitir campos de texto: un string da TS2345. En la LTS (Angular 21) sí compilan con number \| string \| null. | Declara igualmente los campos como number \| null, y tu código pasará a 22 sin tocarlo. |
5.4. La opción when en validadores | 🆕 22.0 | email, min, max, minLength, maxLength y pattern no la admiten en la LTS (Angular 21): da TS2353 y, si fuerzas el tipo, la opción se ignora sin avisar y el validador se aplica siempre. required sí la tiene en las dos versiones. | Envuelve el validador en applyWhen, que funciona igual en 21 y 22. |
7. Fechas con minDate y maxDate | 🆕 22.0 | minDate, maxDate, minDateError y maxDateError no existen en la LTS (Angular 21). | Escribe un validate que compare las fechas con getTime(); el código completo está en esta misma sección. Se pierde el atributo min/max automático en el <input>, y la señal min() del campo. |
8.3. getError | 🆕 22.0 | FieldState.getError no existe en la LTS (Angular 21). | Usa campo().errors().find((e) => e.kind === 'min'), que da el mismo error, aunque sin tipar sus datos propios. |
| 8.4. Clases CSS | 🔄 22.0 | La función de cada clase recibe un FormFieldBinding en lugar del FormField de la LTS (Angular 21). | El ejemplo compila igual, porque los dos tipos tienen state, element y focus. |
8.5. markAsTouched marca a los hijos | 🔄 22.0 | markAsTouched() marca también a los hijos, y admite { skipDescendants: true } para marcar solo ese nodo. En la LTS (Angular 21) marca únicamente el nodo en el que lo llamas, y no recibe argumentos. | f().markAsTouched() no muestra los errores de los campos; llámalo en cada campo, o usa submit, que sí los marca todos en las dos versiones. |
| 9.3. El estado es de solo lectura dentro del contexto | 🔄 22.0 | state, stateOf y fieldTreeOf son de solo lectura: state.value.set(...) da TS2339, igual que stateOf(p.x).markAsTouched(). En la LTS (Angular 21) compila. | No escribas nunca desde una regla; una regla debe calcular, no modificar. Así el código sirve en las dos versiones. |
10.1. disabled, hidden y readonly con { when } | 🔄 22.0 | Pasar la condición directamente, como hidden(p.x, fn), queda obsoleto; se escribe hidden(p.x, { when: fn }). Las funciones no están obsoletas: solo esa forma de llamarlas. | Usa hidden(p.x, fn), porque { when } da TS2353 en 21 y, si fuerzas el tipo, falla al ejecutar con fn is not a function. La forma antigua sigue compilando en 22 con un aviso de obsoleto. |
10.2. hidden permanente | 🆕 22.2 | hidden(ruta) sin condición no compila en la LTS (Angular 21): da TS2554, porque allí la función es obligatoria. | Escribe hidden(p.x, () => true), que hace lo mismo. |
11.3. Opciones debounce y when | 🆕 22.0 | Estas opciones no existen en validateHttp ni en validateAsync en la LTS (Angular 21): dan TS2353. | Para el retraso usa la regla debounce(p.email, 400) del apartado anterior, que retrasa todo el campo; en vez de when, devuelve undefined en request, o params, si no quieres petición. |
11.4. validateAsync | 🔄 22.0 | factory puede devolver cualquier Resource, mientras que en la LTS (Angular 21) debe ser un ResourceRef. | resource(...) y httpResource(...) devuelven ResourceRef, así que el ejemplo compila igual. |
11.5. reloadValidation | 🆕 22.0 | FieldState.reloadValidation no existe en la LTS (Angular 21). | Crea una señal contador, léela en request o en params, e increméntala para forzar otra petición. |
14.3. FormRoot y submission | 🔄 22.0 | En 22, FormRoot funciona sin la opción submission: solo evita que el navegador recargue la página. En la LTS (Angular 21), enviar un FormRoot sin submission lanza NG01915: Cannot submit form with no submit action. | Declara siempre submission en form; FormRoot, submission y onInvalid existen desde 21.2.0, así que el ejemplo compila igual. |
17.1. FormValueControl | 🔄 22.0 | touched pasa a ser solo de entrada y el aviso de que el control se ha tocado se hace con un output llamado touch. En la LTS (Angular 21) este touch se ignora y hay que declarar touched = model(false) y escribir en él. | Cambia las líneas de touched y touch como se indica abajo; el resto del control es igual. |
| 17.4. Controles propios en formularios clásicos | 🆕 22.0 | Un FormValueControl también funciona con [formControl] y [(ngModel)]. En la LTS (Angular 21) no es así, y estas directivas necesitan un ControlValueAccessor. | Si tu control debe servir en los dos mundos, implementa ControlValueAccessor como en el módulo 17; FormField también sabe usarlo. |
17.5. El método reset del control | 🆕 22.0 | En la LTS (Angular 21) nadie llama al método reset() de un control propio. | No hay alternativa directa; si el control guarda estado interno, deriva ese estado de value() con un computed para que se actualice solo al cambiar el valor. |
| 18. Metadatos | 🔄 22.0 | createManagedMetadataKey pasa a recibir (estado, datos), y la clave MIN deja de dar el número: en 22, metadata(MIN) da la clave elegida según el tipo del campo (MIN_NUMBER o MIN_DATE), y lo mismo ocurre con MAX. | En createManagedMetadataKey el único parámetro son los datos. Para leer límites usa f.x().min?.(), que funciona igual en 21 y 22; createLimitSelectionKey no existe y hasMetadata no está en los tipos. |
19.3. extractValue | 🆕 22.0 | extractValue no existe en la LTS (Angular 21). | Recorre el modelo a mano y lee cada control clásico con .value, o llama a .getRawValue(). |
19 - Gestión del estado
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
| Un carrito compartido | 🆕 22.0 | No disponible | En lugar de @Service(), escribe @Injectable({ providedIn: 'root' }), que hace lo mismo; todo lo demás de esta sección funciona igual. |
| Estado del servidor en un servicio | 🔄 22.0 | httpResource pasa de experimental a estable. En la LTS (Angular 21) es experimental. | Este código compila igual si cambias @Service() por @Injectable({ providedIn: 'root' }), pero httpResource puede cambiar entre versiones. |
| Alcance: quién comparte la instancia | 🆕 22.0 | No disponible | En lugar de @Service({ autoProvided: false }), escribe @Injectable() sin opciones y ponlo en providers; el comportamiento es el mismo. |
| 4. Instalar NgRx: la versión importa | 🔄 22.0 | NgRx 22 exige Angular 22 (su dependencia es "@angular/core": "^22.0.0"), así que no se instala en la LTS (Angular 21). | Instala NgRx 21, cuya última versión es la 21.1.1 y exige ^21.0.0, por ejemplo con npm install @ngrx/signals@21. |
8. withProps y withLinkedState | 🔄 22.0 | withProps y withLinkedState existen igual en NgRx 21, pero este ejemplo usa httpResource, que en la LTS (Angular 21) es experimental. | El mismo código compila con NgRx 21.1.1; revisa httpResource en las notas de cada actualización. |
@ngrx/signals/resource | 🆕 22.0 | : el subpaquete llega con NgRx 22, y es experimental. | Conserva el valor durante la carga con resourceFromSnapshots, como en la sección 10 del módulo 13. |
| Reutilizar tipos entre funciones | 🆕 22.0 | : el tipo SignalStoreFeatureType llega con NgRx 22. | Escribe a mano lo que exiges, por ejemplo { state: type<{ foo: string }>() }. |
20 - Angular Material y CDK
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
| Todo el módulo | 🔄 22.0 | Angular Material y el CDK 22 exigen Angular 22 (^22.0.0 \|\| ^23.0.0), así que no se instalan sobre la LTS (Angular 21). | Instala Material y CDK 21.2.14, que es lo que ng add @angular/material elige por sí solo en un proyecto Angular 21. Las API de este módulo son las mismas, salvo los apartados que llevan su propia marca. |
| Qué versión de Material necesitas | 🔄 22.0 | Material 22 solo admite Angular 22 o superior. | Usa la rama 21 de Material, cuya última versión es la 21.2.14 (etiqueta v21-lts de npm); admite Angular 21 y también Angular 22. |
| Signal Forms con Material | 🔄 22.0 | Signal Forms pasa de experimental a estable. | Este ejemplo compila y funciona igual con Material 21.2.14, pero la API es experimental y puede cambiar. |
| Ordenar y paginar en el servidor | 🔄 22.0 | httpResource pasa de experimental a estable. | Este apartado funciona igual, con Material 21.2.14, pero httpResource es experimental y puede cambiar. Si prefieres no depender de él, usa HttpClient con toObservable y switchMap, como en el módulo 15. |
| Cierre al navegar | 🔄 22.0 | disposeOnNavigation admite, además de true y false, los valores 'url-change' y 'pop-state'. | En CDK 21 solo es booleano, y true cierra la capa con los botones Atrás y Adelante. Para cerrarla en cualquier cambio de URL, suscríbete a Router.events y llama a dispose(). |
| Diferencias entre Material 21 y 22 | 🔄 22.0 | Las API públicas de Material y CDK casi no cambian entre 21.2.14 y 22.2.5. | Todo lo explicado sin marca funciona igual, y solo te faltan los pocos símbolos de la tabla siguiente, que este curso no necesita, salvo el valor de disposeOnNavigation ya marcado en el apartado 8. |
21 - Angular Aria y accesibilidad
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
| Todo el módulo | 🔄 22.0 | @angular/aria es estable desde esta versión. En la LTS (Angular 21) existe, pero está en developer preview y su API es distinta en varios puntos. | Instala @angular/aria@21 y @angular/cdk@21; pestañas y acordeón se escriben igual, y lista, combobox, árbol, menú y barra de herramientas llevan su propia marca con los cambios. |
| 5. Angular Aria: patrones sin estilos | 🔄 22.0 | @angular/aria pasa de developer preview a estable. | Instala npm install @angular/aria@21 @angular/cdk@21; la API funciona, pero puede cambiar entre versiones, y de hecho cambia en 22 como indican las marcas de este módulo. |
6. Pestañas con ngTabs | 🔄 22.0 | Las directivas de pestañas pasan de developer preview a estables, sin cambios en su API. | Este apartado se escribe igual con @angular/aria@21; lo hemos compilado y ejecutado en 21.2. |
| 7. Acordeón | 🔄 22.0 | Las directivas de acordeón pasan de developer preview a estables, sin cambios en su API. | Este apartado se escribe igual con @angular/aria@21; lo hemos compilado y ejecutado en 21.2. |
8. Listas de selección con ngListbox | 🔄 22.0 | Listbox pasa a estable y su modelo de selección se llama value en lugar de values. | Escribe [(values)]="seleccionados"; el resto de este apartado funciona igual, y lo hemos ejecutado en 21.2. |
| 9. Combobox y autocompletado | 🔄 22.0 | Combobox es estable y tiene otra estructura: ngCombobox va directamente en el input, y el popup usa ngComboboxPopup y ngComboboxWidget. | Envuelve el input en <div ngCombobox filterMode="manual">, marca el campo con ngComboboxInput y usa <ng-template ngComboboxPopupContainer>, como en el código del final de este apartado. |
| 10. Árboles | 🔄 22.0 | Tree pasa a estable y su modelo de selección se llama value en lugar de values. | Escribe [(values)]="seleccion"; el resto del apartado funciona igual, y lo hemos ejecutado en 21.2. |
| 11. Menús, barras de herramientas y cuadrículas | 🔄 22.0 | Menú, barra de herramientas y cuadrícula pasan a estables. | En 21 value es obligatorio en ngToolbarWidget y ngMenuItem, y ngMenuBar usa values; añade value="exportar" a cada control de la barra. En la cuadrícula, enableRangeSelection existe solo en 21. Lo demás funciona igual. |
| Harnesses de Angular Aria | 🆕 22.0 | No disponible | Localiza los elementos por su rol y pulsa sobre ellos en la prueba, por ejemplo con fixture.nativeElement.querySelectorAll('[role=tab]'). |
22 - Estilos: Tailwind en Angular
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
| La tabla de empleados | 🔄 22.0 | httpResource pasa de experimental a estable. En la LTS (Angular 21) es experimental. | Este ejemplo compila y funciona igual, pero httpResource puede cambiar entre versiones; si no quieres depender de ello, carga los datos con HttpClient como en el módulo 12. |
| Paso 3: el servicio | 🆕 22.0 | No disponible | En lugar de @Service(), escribe @Injectable({ providedIn: 'root' }), importado de @angular/core; el resto del apartado funciona igual. |
| Enlazar la señal o la función sin llamarla | 🔄 22.2 | [animate.enter]="miSenal" y [animate.enter]="miFuncion", sin paréntesis, funcionan desde 22.2. En la LTS (Angular 21) compilan, pero no animan nada. | Llama siempre a la señal o a la función en la plantilla: [animate.enter]="miSenal()". |
| Salidas anidadas | 🔄 22.0 | Si se borra un contenedor, las salidas de sus descendientes de la misma plantilla ahora se esperan. En la LTS (Angular 21) solo se anima el elemento raíz que se borra; lo de dentro desaparece al momento. | Pon animate.leave en el elemento más externo del @if, que sí se espera. |
| Ejercicio 9: Diálogo con doble salida | 🔄 22.0 | La salida del elemento interior solo se ejecuta desde Angular 22, porque es anidada. | La capa exterior se anima y se espera igual, pero la caja interior no hace su scale; o bien aceptas ese cambio, o pones una sola animación que afecte a ambas en la capa. |
23 - Rendimiento y @defer
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
idle con tiempo máximo | 🆕 22.0 | on idle(1500) obliga a cargar tras ese tiempo aunque el navegador siga ocupado. No existe en la LTS (Angular 21), donde da NG5002: "idle" trigger cannot have parameters. | Combina dos disparadores, on idle; on timer(1500ms), que da el mismo resultado. |
Personalizar idle | 🆕 22.0 | provideIdleServiceWith sustituye el servicio que decide cuándo está libre el navegador. No existe en la LTS (Angular 21). | No hay alternativa directa; on idle usa siempre requestIdleCallback. Para otro criterio, usa when con tu propia señal. |
Aviso por prefetch sin disparador principal | 🔄 22.0 | @defer (prefetch on idle) sin disparador principal da el aviso NG8021 al compilar. En la LTS (Angular 21) compila sin avisos. | Escribe siempre un on ... junto al prefetch, para que se comporte como esperas. |
15. Aislar errores con @boundary | 🆕 22.2 | @boundary atrapa los errores de pintado de una parte de la plantilla. No existe en la LTS (Angular 21), donde da NG5002. | No hay nada equivalente; evita que la plantilla lance errores comprobando hasValue() antes de leer un recurso y registra lo demás en un ErrorHandler global. |
Registrar los errores con onViewError | 🆕 22.2 | ErrorHandler añade el método opcional onViewError(error, detalles), y ErrorDetails está en developer preview. No existe en la LTS (Angular 21). | Registra los errores en handleError, que es por donde pasan todos allí. |
| Errores en componentes dinámicos | 🆕 22.2 | createComponent, ViewContainerRef.createComponent y createEmbeddedView admiten la opción onError. No existe en la LTS (Angular 21). | Envuelve la llamada con try/catch para los errores de creación; para los de pintado no hay alternativa, y debes evitarlos en el propio componente. |
| Ejercicio 9: Una barrera por tarjeta | 🆕 22.2 | El ejercicio usa @boundary, en developer preview, que no existe en la LTS (Angular 21). | Resuélvelo con hasValue() y error() en el propio componente, como en la alternativa de la sección 15. |
24 - SSR e hidratación
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
Lo que genera ng add en la LTS | 🔄 22.0 | ng add @angular/ssr escribe provideClientHydration(). En la LTS (Angular 21) escribe provideClientHydration(withEventReplay()), porque allí event replay no viene activado. | Conserva esa opción y añade withIncrementalHydration() si quieres usar los disparadores hydrate de la sección 6. |
Hosts permitidos: el 400 de la primera ejecución | 🔄 22.0 | Sin allowedHosts, el servidor compilado responde 400 a todo. En la LTS (Angular 21) responde 200 con la aplicación vacía, de modo que se pierde el SSR sin ningún error visible. | Declara también allowedHosts; solo con la lista hecha obtendrás el HTML generado en el servidor, y un host desconocido recibirá 400 igual que en 22. |
| Cabeceras de proxy | 🔄 22.0 | Las cabeceras Forwarded y X-Forwarded-* se ignoran por defecto. En la LTS (Angular 21), X-Forwarded-Host y X-Forwarded-Proto se aceptan salvo que digas lo contrario. | Escribe new AngularNodeAppEngine({ trustProxyHeaders: false }) si no tienes un proxy de confianza delante, para tener la misma protección. |
| 6. Hidratación incremental | 🔄 22.0 | provideClientHydration() ya incluye la hidratación incremental. En la LTS (Angular 21) hay que pedirla. | Escribe provideClientHydration(withIncrementalHydration()); sin esa opción, los bloques hydrate se ignoran sin ningún aviso al compilar y su contenido no llega en el HTML del servidor. |
Desactivarla: withNoIncrementalHydration | 🆕 22.0 | No disponible | No hace falta, porque allí la hidratación incremental está desactivada mientras no añadas withIncrementalHydration(). |
| 7. Event replay: que no se pierda ningún clic | 🔄 22.0 | Event replay va activado con provideClientHydration(), porque depende de él la hidratación incremental. En la LTS (Angular 21) no lo está. | Añade withEventReplay(), que ng add ya escribe por ti, o withIncrementalHydration(), que también lo activa. |
resource con id | 🆕 22.0 | La opción id de resource no existe en la LTS (Angular 21), donde da un error TS2769 ('id' does not exist in type). | Usa httpResource, que ya pasa por la caché de HttpClient, o guarda el valor con TransferState, como en el ejemplo anterior. |
Respuestas grandes: maxResponseBodySize | 🆕 22.0 | , donde provideServerRendering no admite opciones y las respuestas de fetch en el servidor no tienen este límite. | Limita el tamaño desde la API, por ejemplo con paginación. |
25 - Internacionalización y PWA
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
| Detectar la conexión con una señal | 🆕 22.0 | No disponible | En lugar de @Service(), escribe @Injectable({ providedIn: 'root' }), importado de @angular/core; todo lo demás funciona igual. |
| Un servicio que avisa al usuario | 🆕 22.0 | No disponible | En lugar de @Service(), escribe @Injectable({ providedIn: 'root' }); SwUpdate funciona exactamente igual. |
19. Notificaciones push con SwPush | 🆕 22.0 | No disponible | En lugar de @Service(), escribe @Injectable({ providedIn: 'root' }); SwPush funciona igual. |
26 - Testing
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
Opciones isolate y quiet de ng test | 🆕 22.0 | No existen en la LTS (Angular 21). | No hay alternativa: el constructor fija isolate: false, así que cuida de no dejar estado global entre ficheros, y la salida de compilación se muestra siempre. |
| Lo que cambia entre Vitest 4 y Vitest 5 | 🔄 22.0 | ng new instala Vitest 5 y jsdom 30. En la LTS (Angular 21) son Vitest 4 y jsdom 28. | Casi todo es igual, pero limpia tú los espías entre pruebas con vi.clearAllMocks() en beforeEach o con clearMocks: true, y pon siempre await en .resolves y .rejects, porque Vitest 4 no te avisa. |
Servicios con @Service | 🆕 22.0 | No disponible | Escribe @Injectable({ providedIn: 'root' }) en cada servicio de este módulo. Las pruebas no cambian: las hemos ejecutado también en la 21 con ese único cambio. |
Recuperar el último fixture: TestBed.getLastFixture | 🆕 22.0 | No disponible | Guarda el resultado de TestBed.createComponent en una variable y pásala a tus funciones auxiliares. |
| Detección y propiedades normales | 🔄 22.0 | Sin changeDetection, el componente es OnPush: tras cambiar una propiedad normal, fixture.detectChanges() no actualiza la vista y no avisa. En la LTS (Angular 21) el mismo código lanza NG0100. | La solución es la misma en ambas versiones: guarda en señales lo que muestra la plantilla y usa await fixture.whenStable(). |
Componentes con httpResource | 🔄 22.0 | httpResource pasa de experimental a estable. | Existe y se prueba igual: estas mismas pruebas se han ejecutado en la 21, pero al ser experimental su API puede cambiar entre versiones. |
| Signal Forms | 🔄 22.0 | Signal Forms pasa de experimental a estable. | Este componente y sus pruebas funcionan sin cambios en la 21, pero la API es experimental; revisa las diferencias del módulo 18. |
TestBed.createDirective | 🆕 22.2 | No disponible | Crea un componente anfitrión con la directiva en su plantilla y búscala con By.directive, como en el apartado siguiente; funciona igual en las dos versiones y es lo que se ha hecho siempre. |
La migración automática de fakeAsync | 🆕 22.0 | La opción --fake-async no existe en la LTS (Angular 21): allí la orden responde Unknown argument: fake-async. | Ejecuta el esquema sin esa opción para convertir Jasmine, y cambia fakeAsync a mano con la tabla de equivalencias de esta sección. |
| Elegir Istanbul como proveedor | 🆕 22.0 | ng test --coverage admite también @vitest/coverage-istanbul. En la LTS (Angular 21) solo se comprueba @vitest/coverage-v8. | Instala @vitest/coverage-v8, que es el que usa este módulo. |
27 - Despliegue y herramientas con IA
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
| 11. WebMCP experimental | 🆕 22.0 | No disponible | Angular no ofrece equivalente. Solo podrías llamar tú a la API del navegador, sin tipos y sin la limpieza automática al destruir el componente. Como la especificación aún cambia, lo razonable es no usarlo todavía. |
| Anotaciones | 🆕 22.2 | No disponible | No hay alternativa, porque WebMCP tampoco existe allí. En 22.0 y 22.1 tampoco hay anotaciones. |
| Herramientas por ruta | 🆕 22.2 | withAutoCleanupInjectors no existe en la LTS (Angular 21), donde se llama withExperimentalAutoCleanupInjectors. | Ese nombre sirve para limpiar los inyectores de ruta, pero WebMCP no existe allí, así que no aplica a este apartado. |
28 - Migraciones y Angular clásico
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
9. De 21 a 22 con ng update, migración a migración | 🔄 22.0 | Todo este apartado describe el paso de la 21 a la 22. | No te afecta; mantén el proyecto en la última 21.2 con ng update @angular/core@21 @angular/cli@21, que solo aplica correcciones, y vuelve aquí cuando decidas subir. |
| 10. Lo que ninguna migración arregla | 🔄 22.0 | Estas API se eliminan en la 22 y el código que las usa deja de compilar. | Siguen existiendo, aunque casi todas obsoletas; cambiarlas ya por su sustituto, que también existe en la 21, te deja el proyecto listo para actualizar. |
| 11. Retirar las protecciones | 🔄 22.0 | Estas protecciones solo existen tras actualizar a la 22. | No hay nada que retirar; tus componentes ya usan Eager (antes Default) y HttpClient ya usa XHR, y puedes pasar a OnPush componente a componente cuando su estado esté en señales. |
service | 🆕 22.1 | El generador service-migration no existe en la LTS (Angular 21), ni el decorador @Service, que es de la 22.0. | Deja @Injectable({ providedIn: 'root' }), que es equivalente; puedes aplicar ya el generador inject para que el cambio sea trivial cuando actualices. |
29 - Proyecto final
| Apartado | Versión | Qué ocurre | Si usas la LTS |
|---|---|---|---|
HttpClient usa fetch por defecto | 🔄 22.0 | En 22, HttpClient usa fetch por defecto y withFetch() está obsoleto. En la LTS (Angular 21) el defecto es XHR. | No tienes que cambiar nada en este proyecto, porque no usa progreso de subida ni nada propio de un backend; si quieres fetch también en 21, añade withFetch() a provideHttpClient. |
6.3. @Service en lugar de @Injectable | 🆕 22.0 | No disponible | Usa @Injectable({ providedIn: 'root' }), que hace lo mismo; compilar @Service() en 21 da TS2305: Module '"@angular/core"' has no exported member 'Service'. |
| 10. Login con Signal Forms | 🔄 22.0 | Signal Forms es estable desde 22. En la LTS (Angular 21) existe en @angular/forms/signals, pero es experimental y puede cambiar. | Este formulario de login compila y funciona igual en 21.2; los cambios del formulario de empleados están en la sección 14. |
isFieldTree | 🆕 22.1 | : importarla da TS2724. | Trata la propiedad como FieldTree<unknown> \| undefined y comprueba que exista, como en la sección 19. |
| 11. El panel de mando | 🔄 22.0 | httpResource es estable desde 22. En la LTS (Angular 21) existe y se usa igual, pero es experimental. | Todo el proyecto usa httpResource sin cambios; revisa las notas de versión al actualizar. |
Herencia de parámetros: paramsInheritanceStrategy | 🔄 22.0 | El valor por defecto de paramsInheritanceStrategy pasa de 'emptyOnly' a 'always'. En la LTS (Angular 21), la hija editar no recibe el :id del padre y el formulario de edición se comporta como un alta, sin ningún error. | Añade withRouterConfig({ paramsInheritanceStrategy: 'always' }) a provideRouter. |
disabled y hidden con { when } | 🔄 22.0 | Pasar la condición directamente, como hidden(p.x, fn), queda obsoleto; se escribe hidden(p.x, { when: fn }). | Escribe disabled(p.jefeId, fn) y hidden(p.comision, fn); en 21, { when } da TS2353: Object literal may only specify known properties, and 'when' does not exist. |