El 16 de noviembre de 2022, Google lanzó la decimoquinta versión principal de su framework Angular.
Al igual que muchos miembros de la comunidad, me siento muy emocionado por las grandes mejoras que trajo al ecosistema, pero también existe un cierto sabor amargo por algunas otras cosas que no gustaron.
Sin embargo; en este post, profundizaré en las nuevas características y docenas de refinamientos y desglosaré lo que significan para el panorama de Angular y cómo mejoran la experiencia del desarrollador y el rendimiento web.
Pero antes de empezar, explicaré algo de la jerga que las acompaña.
Table of Contents
Lingo
∘ Function composition
∘ HMR
∘ @keyframes
∘ Lazy loading
∘ Stack trace
∘ Tree shaking
New Features
1. Directive Composition API
2. Stable Standalone APIs
3. Tree-shakable Router API
4. HTTP with provideHttpClient
5. Functional Router Guard
6. Dynamic Router Outlet Names
7. Easy Lazy Loading
8. Stable Image Directive
9. Better stack traces
10. Mistyped banana in the Box
11. Component-Scoped keyframes
12. Compatibility for MDC Components
13. CDK Listbox
Additional Improvements
∘ Ivy Landmark / Better Performance
∘ Angular DevTools
∘ Angular CLI
∘ More utility in forms package
∘ Deprecated Protractor
∘ Improvements in the esbuild support
Final Thought
Lingo
Composición de funciones
Cuando pasamos el resultado de una función a la siguiente función y luego el resultado de ésta a otra hasta obtener un resultado final de la última función ejecutada, llamamos a este proceso en JavaScript composición de funciones.
// Instead of passing the result of one function to another this way:
func1(func2(func3(15)))
// You can define a compose function (if you're not using a library like lodash or ramda)
const compose = (…fns) => x => fns.reduceRight((y, f) => f(y), x)
compose(func1, func2, func3)(15)
Si tu quieres conocer el punto de vista de las cosas que gustaron y que no gustaron tanto de Angular 15 ve al siguiente video.
HMR
Mientras una aplicación se ejecuta en el navegador, podemos añadir, eliminar o intercambiar módulos sin una recarga completa. A este proceso lo llamamos Hot Module Replacement (HMR).
Ahorra un valioso tiempo de desarrollo porque actualiza instantáneamente sólo lo que ha cambiado en la aplicación cuando cambia el código fuente.
A diferencia de una recarga completa, HMR mantiene el estado de la aplicación y puede acelerar significativamente el desarrollo.
@keyframes
La regla @keyframes
en CSS especifica los pasos intermedios de una animación que cambia gradualmente de un conjunto de estilos a otro.
Utilizamos las palabras clave "de" y "a" o 0% y 100% para determinar este cambio. He aquí un ejemplo
@keyframes slidein {
from {
transform: translateX(0%);
}
to {
transform: translateX(100%);
}
}
Lazy Loading
Para mejorar el rendimiento y el uso de los recursos del sistema, retrasamos la inicialización o carga de los módulos y objetos hasta que son requeridos por la aplicación. A este patrón de diseño lo denominamos carga perezosa.
Stack trace
Representan los dos lugares donde la memoria se asigna dinámicamente a un programa mientras se ejecuta.
El rastreo de pila es el informe de las pilas de ejecución en un momento determinado durante la ejecución de un programa.
Los desarrolladores suelen utilizar las trazas de pila durante las sesiones de depuración interactivas o post-mortem
Tree shaking
Tree shaking en JavaScript es el proceso de eliminar código muerto. El resultado sería un bundle de tamaño mínimo.
Mediante la comprobación de las declaraciones de importación y exportación, los agrupadores de módulos como webpack y Rollup eliminan automáticamente el código que no es utilizado por su aplicación.
API de composición de directivas
Directive Composition es una feature request en GitHub que ha sido demandada durante seis años antes de ser implementada en Angular 15.
Minko Gechev
jefe de relaciones con desarrolladores de Google e ingeniero del equipo de Angular, la describió como una nueva forma de componer la lógica de la interfaz de usuario porque permite reutilizar el código más allá de la herencia clásica.
Similar a la composición de funciones en JavaScript, la composición de directivas permite combinar directivas individuales, en componentes y otras directivas, en una más compleja aprovechando la propiedad hostDirectives
:
En el ejemplo anterior, estamos utilizando la directiva HasColor
y las entradas y salidas especificadas de CdkMenu
.
Recuerde que sólo se pueden utilizar directivas independientes para la composición.
Stable Standalone APIs
Los componentes autónomos y las APIs son una característica notable introducida en Angular 14. Facilitan la creación de aplicaciones sin NgModule
. Con esta nueva versión, la API se vuelve estable y deja de estar en estado Developer Preview.
API de Router estable
La API independiente de Router con la función provideRouter()
añadida en Angular 14.2 también tiene ahora un estado estable.
const appRoutes: Routes = [];
bootstrapApplication(AppComponent,
{
providers: [
provideRouter(appRoutes,
withDebugTracing(),
withRouterConfig({paramsInheritanceStrategy: 'always'}))
]
}
);
La API independiente del enrutador permite agitar el árbol, lo que conduce a la eliminación de código superfluo. Cuando los bundlers eliminan el código innecesario durante la compilación, obtenemos una implementación de enrutador un 11% más ligera.
HTTP con provideHttpClient
De la misma forma que usamos la función provideRouter()
en la API Router, en el nuevo mundo de Angular 15, podemos usar provideHttpClient()
para proporcionar el HttpClienten
una app donde los módulos son opcionales.
La misma lógica se aplica a los interceptores HTTP. Podemos definirlos como funciones usando withInterceptors()
:
bootstrapApplication(AppComponent, {
providers: [provideHttpClient(withInterceptors([authInterceptor()]))]
});
O utilizando withInterceptorsFromDi()
para registrar un interceptor basado en una clase:
bootstrapApplication(AppComponent, {
providers: [provideHttpClient(withInterceptorsFromDi([AuthInterceptor]))]
});
Guardia de enrutador funcional
Además de la API Tree Shakeable Router, Angular 15 trae otras innovaciones para reducir el código boilerplate. Los nuevos Functional Router Guards ofrecen un enfoque más ágil para implementar Guards directamente en la declaración, como podemos ver en el siguiente ejemplo:
Con el enfoque tradicional, implementamos un AuthGuard
e inyectamos un AuthService
en él para comprobar si el usuario está autenticado o no en el método canActivate()
:
// The traditional approach: Implement AuthGuard and inject AuthService in it
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService) {}
canActivate() {
return this.authService.isAuthenticated();
}
}
const route = {
path: 'dashboard',
canActivate: [AuthGuard]
};
Con Functional Router Guards, podemos deshacernos de todo el código repetitivo e inyectar el AuthService
y llamar a su método isAuthenticated()
en la declaración de nuestra ruta:
const route = {
path: 'dashboard',
canActivate: [() => inject(AuthService).isAuthenticated()]
};
Dynamic Router Outlet Names
Ahora en Angular 15 podemos establecer dinámicamente el nombre de la salida del enrutador.
Por ejemplo, podemos vincularlo a una variable desde un bucle for, lo que era imposible en el pasado. Esto es un cambio de juego que nos permite escribir componentes elásticos robustos. Así que así es como se va a escribir dentro de un bucle:
<ng-container *ngFor="let outlet of outlets">
<router-outlet [name]="outlet"></router-outlet>
</ng-container>
Puede pasar la salida como parámetro de entrada.
Carga Lenta Fácil
Antes, cuando cargabas perezosamente tus componentes o sus hijos en el módulo de enrutamiento, debías seleccionar qué cargar siguiendo la sintaxis de lazy loading, que es un poco compleja.
Personalmente nunca la he memorizado. Cada vez que necesito usarla, la copio/pego de otro módulo o proyecto y la ajusto:
{
path: 'dashboard',
loadChildren: () => import('./modules/dashboard/dashboard.module').then((m) => m.DashboardModule)
}
@NgModule({ ... })
export class DashboardModule {}
La nueva sintaxis en Angular 15, llamada router unwraps default imports, es mucho más fácil. Si eres, como yo, un fan de escribir menos código boilerplate, te encantará.
Todo lo que necesitas hacer es utilizar export default para especificar qué componente debe cargarse por defecto. Entonces puedes deshacerte de la operación .then(..)
:
{
path: 'dashboard',
loadChildren: () => import('./modules/dashboard/dashboard.module')
}
@NgModule({ ... })
export default class DashboardModule {}
Directiva de imagen estable
La directiva de imagen NgOptimizedImage
, añadida al framework con Angular 14.2, es ahora estable en la versión actual. Optimiza el rendimiento web y las puntuaciones de Core Web Vitals - Land's End, por ejemplo, ha sido testigo de una mejora del 75% en su Largest Contentful Paint (LCP) después de usar esta directiva.
Angular 15 también ofrece lo siguiente:
- Una mejora en los avisos de imágenes
- Un tiempo de descarga reducido gracias a una generación automática de la directiva srcset.
- Un modo de relleno experimental: Permite que una imagen llene su contenedor padre cuando no se especifican sus dimensiones.
Mejores rastros de pila (Better Stack Traces)
Con la nueva API de etiquetado de pila asíncrono desarrollada en colaboración con el equipo de Chrome DevTools, ahora no tenemos más basura de zone.js en las trazas de pila y tenemos una mejor experiencia de depuración.
Banana en la Caja
"banana in a box" es un término que describe la sintaxis de vinculación de dos formas en una plantilla de Angular. Probablemente lo has visto varias veces cuando se utiliza ngModel
:
<mat-select [(ngModel)]="entity.status">
<ng-container *ngFor="let status of statusList">
<mat-option [value]="status.id">{{ status.name }}</mat-option>
</ng-container>
</mat-select>
A veces los desarrolladores escriben el paréntesis (la banana) y el corchete (la caja) no en el orden correcto poniendo paréntesis fuera de la caja ([]), lo que lleva a muchos errores.
La corrección en la nueva versión de Angular informará de estos problemas y ofrecerá una solución a través del servicio de lenguaje. Dependiendo de tu IDE, puede que necesites o no instalar @angular/language-service
en tu proyecto y añadirlo a tu package.json
. WebStorm, por ejemplo, ya no lo requiere desde su versión 2019.1:
npm install --save-dev @angular/language-service
Component-Scoped Keyframes
Durante mucho tiempo, hubo un problema con los keyframes CSS en Angular.
Aunque el framework proporciona un Emulated viewEncapsulation que evita colisiones de nombres de la hoja de estilos, las animaciones CSS @keyframes
no estaban bloqueadas al selector de atributos de un componente. Permanecían disponibles globalmente, y como resultado, se filtraban o solapaban con otros keyframes con el mismo nombre.
En Angular 15, si tiene los mismos nombres de fotogramas clave en varios lugares en su aplicación, no van a colisionar a través de las definiciones de componentes y la representación, ya que ahora son componente de alcance.
El equipo de Angular ha abordado este problema, que ha existido durante años, simulando el nombre de los fotogramas clave con el selector del componente anfitrión.
Este cambio puede romper tu código si dependes de un aspecto global de los keyframes. En tal caso, necesitas moverlos a tu hoja de estilos global.
Compatibilidad para componentes MDC
Para ofrecer una mejor alineación con la especificación Material Design y adoptar los estilos de los componentes Material 3, el equipo de Angular ha refactorizado los componentes materiales de Angular basándose en Material Design Components for Web (MDC).
Podrás notar una actualización en los estilos y estructuras DOM de muchos de estos componentes y que algunos son reescritos desde cero. Así que probablemente necesites ajustar tu proyecto al migrar a Angular 15.
CDK Listbox
En Angular 15, el paquete Component Dev Kit (CDK) incluía un módulo @angular/cdk/listbox
basado en el patrón listbox de WAI ARIA:
Este nuevo módulo CDK ayuda a crear interacciones listbox personalizadas gracias a las directivas que proporciona. Ofrece funciones de accesibilidad (A11y) como la compatibilidad con el diseño bidi, la interacción con el teclado y la gestión del foco.
Mejoras adicionales
Con Angular 15, también tenemos los siguientes cambios:
Ivy Landmark / Mejor rendimiento: La última versión de Angular ofrece una construcción cómoda, y la reconstrucción de HMR es más fácil de habilitar.
Mejores stack traces: Angular DevTools ofrece ahora una vista previa de la depuración de inyección de dependencias.
Angular CLI: al crear un nuevo espacio de trabajo Angular con ng new myAppName
, obtendrás una salida simplificada y menos archivos generados.
El paquete forms tiene más funciones de utilidad.
Protractor obsoleto: Basándose en los comentarios de la comunidad, el equipo de Angular anunció Protractor, el marco de pruebas e2e, como obsoleto, y llegará al final de su vida útil en el verano de 2023. Las soluciones alternativas de pruebas de extremo a extremo que puede utilizar para su proyecto Angular incluyen Cypress, Nightwatch y WebdriverIO.
La nueva versión ha mejorado el soporte experimental de esbuild, que se introdujo en Angular 14. Para probar esbuild, necesita actualizar su constructor en angular.json
como se indica a continuación:
"builder": "@angular-devkit/build-angular:browser-esbuild"
Reflexión final
Angular ha sufrido problemas de rendimiento en comparación con librerías ligeras como React. Pero después de eliminar el compilador y el motor de renderizado heredados y sustituirlos por Ivy, el equipo de Angular y los colaboradores de la comunidad han implementado nuevas mejoras, que harán que Angular sea ultrarrápido.
Como puedes ver, hay muchos cambios en la versión 15 del framework. Si todavía no te has mojado los pies en él, espero que hayas sacado algunas ideas de este post y te sientas más preparado para usarlo en tus proyectos.