Angular 14

Featured image

Migración Angular 14

Buenasss como andan? Volvemos con una nueva migra de angular!! Por acá comparto la agenda de los releases y versiones oficiales.

El post de hoy va dedicado a las experiencias durante una migración específica desde un previo Angular 13 hacia Angular 14, comparto novedades, tips, errores y sus respectivos fixes para renovarnos y sacarlo con fritas!

Memes About 'The Office' Revival Rumors Are The Internet At Its Best

Novedades

Repasemos algunas novedades lindas que trae esta versión.

Standalone Components

Antes de que saltes de alegría y te metas a codear standalone components, debo advertir que en ng14 esta feature está en modo DEVELOPER PREVIEW. Lo que significa que, se pueden explorar y experimentar pero a modo dev, NO están en su punto stable aún. Por lo que muy probablemente su API sufra cambios en las próximas versiones.

Ahora sí, volviendo al tema, el objetivo de los componentes standalone (independientes) de Angular es agilizar la creación de aplicaciones Angular reduciendo la necesidad de NgModules. Info completa y oficial de los standalonitos por acá

Nota: Las clases de Angular marcadas como standalone, no necesitan ser declaradas en un NgModule (de hecho el compilador de Angular reporta un error si lo intentas).

Ejemplo: Creamos Standalone component

Los componentes independientes pueden especificar sus dependencias directamente, en lugar de obtenerlas a través de NgModule. Por ejemplo, si PhotoGalleryComponent es un componente independiente, puede importar directamente otro componente independiente ImageGridComponent:

@Component({
  standalone: true,
  selector: "photo-gallery",
  imports: [ImageGridComponent],
  template: ` ... <image-grid [images]="imageList"></image-grid> `,
})
export class PhotoGalleryComponent {
  // component logic
}

Ejemplo: Cargamos Standalone component (Lazy loading)

También, cualquier ruta puede cargar un componente independiente mediante loadComponent (en lugar de cargar un módulo):

export const ROUTES: Route[] = [
  {
    path: "admin",
    loadComponent: () =>
      import("./admin/panel.component").then((mod) => mod.AdminPanelComponent),
  },
  // ...
];

Pero bueno, no te quiero tentar con esta feature, mas adelante podremos dar el salto de fé y empezar a experimentarlos cuando esté en una etapa más estable.

Change detection

En Angular, la detección de cambios está muy optimizada y es muy eficaz, pero puede provocar ralentizaciones si la app ejecuta esta detección con demasiada frecuencia.

En esta guía que dejo por acá, podemos ver formas de controlar y optimizar el mecanismo de detección de cambios omitiendo partes de nuestra app y ejecutando la detección de cambios sólo cuando sea realmente necesario.

Diagnósticos

Muchas veces puede pasar que, parte de lo que codeamos resulta técnicamente válido, tanto para el compilador como para el tiempo de ejecución, SIN EMBARGO, no significa que estemos libres del mal, de bugs, por más que técnicamente esté “ok”. Es por eso, que el compilador de Angular incluye “diagnósticos extendidos” que identifica muchos de estos casos, con el fin de advertirnos sobre los posibles problemas y así poder cumplir con las mejores prácticas.

Estos son los actualmente soportados:

Para entrar en más detalles y lecturas interesantes de cada uno, comparto la docu oficial acá

Angular CLI

Algunas mejoras de Angular CLI que vale la pena destacar: ng completion Para errores tipográficos en líneas de comandos, tenemos el nuevo ng completion de la v14 que introduce el autocompletado en tiempo real.

Angular DevTools offline y en Firefox

Dejo por acá la DevTools de Firefox

Guía de migración

Bueno ahora sí, manos a la obra. Para empezar vamos a la guía oficial aquí. En este caso, viniendo de la versión 13, no necesitamos aplicar cambios en nuestro código ANTES de ejecutar el comando para poder migrar. Por lo tanto, podemos arrancar:

ng update @angular/core@14 @angular/cli@14

La mayoría de cambios requeridos los aplicamos con el comando. El resto de posibles cambios que tengamos que hacer, dependerán como siempre del proyecto en el que estemos trabajando y esta guía podría cubrir varios de esos casos.

Cambios

Para esta migración debemos tener en cuenta algunos cambios fundamentales:

Problemas durante la migración

Como en toda migración, hay cierta incertidumbre en cuanto a qué problemas podemos llegar a encontrar en el camino. En términos generales, si venis de una versión relativamente nueva como podría ser ng13, tus tests están en Jasmine , los ejecutas con Karma y nada raro, ninguna dependencia extraña o nada loco, la vida te sonreirá y migrarás sin mayores inconvenientes mas que algun que otro tema de tipado o issue dentro de todo simple de resolver. Ultimamente, los errores son mas explicitos y claros, haciendo que podamos fixearlos con mayor rapidez y sencillez.

Podría haber problemas de compatibilidad con algunas dependencias y librerías externas también, para esto habría que chequear si es posible actualizarlas o bien analizar distintas alternativas para ajustar esas dependencias.

Veamos algunos puntos y problemas comunes.

Jest

Si estás usando Jest para los unit tests, te recomiendo las siguientes versiones que van bien con ng14:

    "jest": "^28.1.3",
    "jest-preset-angular": "^12.2.3",
    "@types/jest": "^28.1.1",
    "ts-jest": "^28.0.8",

Error

En caso de que te encuentres con este error:

    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import * as i0 from '@angular/core';
                                                                                                                                               ^^^^^^
        SyntaxError: Cannot use import statement outside a module

Solución

Es necesario que ajustes tu jest.config, con el transformIgnorePatterns y las dependencias en cuestión:

    // jest.config.js
    module.exports = {
      // ...other options
      transformIgnorePatterns: ['node_modules/(?!@angular|@ngrx)'],
    };

Teoría y explicación

Esto pasa porque por defecto, Jest NO transforma node_modules, porque deberían ser archivos JavaScript válidos. Sin embargo, los autores de las librerías asumen que compilarás sus fuentes. Así que en estos casos, hay que decirle esto a Jest explícitamente. El código de arriba significa que @angular, @ngrx serán transformados, a pesar de que son node_modules.

Ahora, si la dependencia que causa el problema es una subdependencia de un paquete node_modules o un módulo diseñado para ser utilizado con nodeJS, podría ser necesario un custom resolver para solucionar el problema. Dado que en estos casos, la regla de transformIgnorePatterns, no es suficiente para agregar esas dependencias a nuestra whitelist.

Error

Entonces, si nos encontramos con problemas entre subdependencias de algun paquete, vamos a encontrarnos con un error como este:

    node_modules\@angular\fire\node_modules\@firebase\firestore\dist\index.esm2017.js:12705
                        function (t, e) {
                        ^^^^^^^^

        SyntaxError: Function statements require a function name

Solución

Para solucionarlo, necesitamos armar un custom resolver para jest, como mencioné más arriba, y esto nos va a servir para algunas dependencias y subdependencias, como ser: Firebase, Ngrx, Rxjs, etc.

El resolver nos quedaría así:

// jest.resolver.js
module.exports = (path, options) => {
  // Call the defaultResolver, so we leverage its cache, error handling, etc.
  return options.defaultResolver(path, {
    ...options,
    // Use packageFilter to process parsed `package.json` before the resolution (see https://www.npmjs.com/package/resolve#resolveid-opts-cb)
    packageFilter: (pkg) => {
      const pkgNamesToTarget = new Set([
        "rxjs",
        "@firebase/auth",
        "@firebase/storage",
        "@firebase/functions",
        "@firebase/database",
        "@firebase/auth-compat",
        "@firebase/database-compat",
        "@firebase/app-compat",
        "@firebase/firestore",
        "@firebase/firestore-compat",
        "@firebase/messaging",
        "@firebase/util",
        "firebase",
        "ngrx",
      ]);

      if (pkgNamesToTarget.has(pkg.name)) {
        // console.log('>>>', pkg.name)
        delete pkg["exports"];
        delete pkg["module"];
      }

      return pkg;
    },
  });
};

Para otros common issues y troubleshooting de jest-preset-angular, comparto algunas soluciones por acá

NX

Ahora si, NX es otra historia y otro mundo en la migración. Como guía para migrar desde versiones anteriores de NX a una compatible con Angular 14, comparto 2 aspectos:

NX Storybook

Con Storybook en un repo con NX, seguramente descubras que no funca nada, tanto para serve como para build del storybook de nuestra app. Muy probablemente estabas usando los builders de NX (@nrwl):

    @nrwl/storybook:storybook
    @nrwl/storybook:build

A partir de ahora, vamos a usar los builders puros de storybook, es decir, los mismos que usamos para servir y buildear storybook en entornos, proyectos y repositorios SIN NX:

@storybook/angular:start-storybook
@storybook/angular:build-storybook

Te dejo más detalles como guía adicional del hilo acá! y también por las dudas, la docu oficial de Storybook para Angular acá

Conclusión

Migrar de Angular 13 a Angular 14 podría volverse complejo en algunas situaciones pero tiene sus beneficios, entre los que más destacto:

Finalmente, comparto referencias y lecturas complementarias por acá

Happy coding!

Beer!