← maurobernal.com.ar

Etiqueta: zoneless

  • Guía práctica: cómo migré un proyecto de Angular 19/20 a Angular 21.2 sin morir en el intento

    Migrar un proyecto Angular de v19/20 a v21 puede sonar intimidante. En la práctica, si seguís el proceso correcto, es incremental: cada paso es reversible y el proyecto sigue funcionando en todo momento. Esta es la guía que apliqué en proyectos reales.

    Antes de empezar: el inventario

    # Ver versión actual y dependencias desactualizadas
    ng version
    ng update
    
    # Verificar compatibilidad antes de actualizar
    npx npm-check-updates -u --target minor

    Paso 1: Actualizar el core

    # Siempre de a una versión mayor a la vez
    # Si estás en v19, primero actualizar a v20, luego a v21
    
    # De v20 a v21:
    ng update @angular/core@21 @angular/cli@21
    
    # El comando aplica schematics automáticos:
    # - Actualiza imports deprecated
    # - Adapta APIs que cambiaron
    # - Avisa sobre cambios manuales necesarios
    
    # Verificar que compila
    ng build --configuration production

    Paso 2: Adoptar Zoneless

    // app.config.ts
    import { provideZonelessChangeDetection } from '@angular/core';
    
    export const appConfig: ApplicationConfig = {
      providers: [
        provideZonelessChangeDetection(),
        provideRouter(routes)
      ]
    };
    
    // angular.json: "polyfills": []  // eliminar zone.js
    // npm uninstall zone.js

    Paso 3: Migrar de Karma a Vitest

    ng generate @angular/build:vitest
    ng test  # verificar que los tests pasan
    npm uninstall karma karma-chrome-launcher karma-coverage karma-jasmine karma-jasmine-html-reporter

    Paso 4: Standalone components

    # Angular 21 asume standalone por defecto
    # Migración automática:
    ng generate @angular/core:standalone --mode=convert-to-standalone
    ng generate @angular/core:standalone --mode=prune-ng-modules
    ng generate @angular/core:standalone --mode=standalone-bootstrap

    Checklist completo

    PasoAcciónObligatorio
    1ng update @angular/core@21 @angular/cli@21✅ Sí
    2Verificar que la app compila y los tests pasan✅ Sí
    3Activar provideZonelessChangeDetection()Recomendado
    4Eliminar Zone.js de polyfillsCon paso 3
    5Migrar a VitestRecomendado
    6Nuevos formularios con Signal FormsGradual
    7Componentes de nav con funciones standalone del RouterGradual
    8Configurar ng mcp para integración con IAOpcional

    Errores comunes y cómo resolverlos

    // ERROR: El componente no se actualiza después de activar Zoneless
    // → Convertir estado a Signals o llamar markForCheck()
    
    // ERROR: Tests fallan con "No current Angular test" después de Vitest
    // → Verificar imports de @angular/core/testing
    
    // ERROR: "Cannot find module zone.js"
    // → Buscar y eliminar import 'zone.js' en el proyecto:
    // grep -r "import 'zone.js'" src/
    
    // ERROR: ExpressionChangedAfterItHasBeenCheckedError en Zoneless
    // → Usar signal.update() o signal.set() en lugar de mutación directa

    Mi experiencia: cuánto tardó la migración

    En un proyecto de tamaño medio (45 componentes, 12 servicios, 80 tests): el ng update tardó 10 minutos. Revisar avisos y corregir deprecaciones: media jornada. Migración a Zoneless con todos los tests adaptados a Vitest: un día. Adopción gradual de Signal Forms en los formularios principales: dos semanas, a medida que tocábamos cada módulo.

    No es un proceso de un fin de semana, pero tampoco es una reescritura. Es una evolución incremental que podés hacer en paralelo con el trabajo normal del equipo.


    Artículo anterior: Router Signals en Angular 21: navegación standalone sin cargar todo el Router | Fin de la Serie Angular 20 → 21.2

    ¿Tenés preguntas sobre la migración en tu proyecto específico? Dejá tu caso en los comentarios. 👇

  • Adiós Zone.js: cómo Angular 21 cambió la detección de cambios para siempre

    Cuando leí «Angular sin Zone.js» por primera vez pensé que era un truco de optimización para casos extremos. Ahora es el estándar. Y una vez que lo entendés, no querés volver. La detección de cambios deja de ser magia negra para convertirse en algo predecible y controlable.

    Qué hacía Zone.js y por qué era problemático

    Zone.js es una librería que parcha todas las APIs asíncronas del browser: setTimeout, setInterval, Promise, addEventListener, XMLHttpRequest. Cuando cualquiera de esas operaciones completa, Zone.js notifica a Angular para que ejecute la detección de cambios en todo el árbol de componentes.

    El problema: esa detección recorre todo el árbol aunque solo cambiara un campo en un componente profundo. En aplicaciones grandes, eso genera renders innecesarios que degradan el INP (Interaction to Next Paint), la métrica Core Web Vital que Google usa para el ranking.

    // Con Zone.js: cualquier click, timeout o promesa dispara detección en TODO el árbol
    // ❌ Angular evalúa todos los componentes aunque nada haya cambiado
    
    // Con Signals: solo los componentes que dependen del Signal actualizado se re-renderizan
    // ✅ Detección quirúrgica y predecible

    Activar Zoneless en Angular 21

    // app.config.ts
    import {
      ApplicationConfig,
      provideZonelessChangeDetection
    } from '@angular/core';
    import { provideRouter } from '@angular/router';
    import { routes } from './app.routes';
    
    export const appConfig: ApplicationConfig = {
      providers: [
        provideZonelessChangeDetection(),
        provideRouter(routes)
      ]
    };
    // angular.json — eliminar zone.js de los polyfills
    {
      "projects": {
        "mi-app": {
          "architect": {
            "build": {
              "options": {
                "polyfills": []
              }
            }
          }
        }
      }
    }
    
    // package.json — desinstalar
    // npm uninstall zone.js

    Signals: el mecanismo que reemplaza a Zone.js

    import { Component, signal, computed, effect } from '@angular/core';
    
    @Component({
      selector: 'app-contador',
      template: `
        <p>Contador: {{ contador() }}</p>
        <p>Doble: {{ doble() }}</p>
        <button (click)="incrementar()">+1</button>
      `
    })
    export class ContadorComponent {
      contador = signal(0);
      doble = computed(() => this.contador() * 2);
    
      constructor() {
        effect(() => {
          console.log(`Contador cambió a: ${this.contador()}`);
        });
      }
    
      incrementar() {
        this.contador.update(v => v + 1);
        // Solo re-renderiza este componente — no el árbol completo
      }
    }

    Signals vs Observables: cuándo uso cada uno

    SituaciónSignalObservable (RxJS)
    Estado local del componente✅ IdealInnecesariamente complejo
    Estado derivado (computed)computed()pipe + map
    Streams de eventos complejosNo aplica bien✅ Ideal
    HTTP requests✅ httpResourceHttpClient + async pipe
    WebSockets / SSECombinado con toSignal()✅ Ideal
    Estado global (store)✅ signals + servicesNgRx / Akita

    toSignal() y toObservable(): el puente entre mundos

    import { toSignal, toObservable } from '@angular/core/rxjs-interop';
    import { HttpClient } from '@angular/common/http';
    import { debounceTime, switchMap } from 'rxjs/operators';
    
    @Component({ ... })
    export class MiComponent {
      private http = inject(HttpClient);
    
      // Observable → Signal (sin async pipe en el template)
      usuarios = toSignal(
        this.http.get<Usuario[]>('/api/usuarios'),
        { initialValue: [] }
      );
    
      // Signal → Observable (para operadores RxJS)
      busqueda = signal('');
      resultados$ = toObservable(this.busqueda).pipe(
        debounceTime(300),
        switchMap(q => this.http.get(`/api/buscar?q=${q}`))
      );
    }

    El impacto real en rendimiento

    En una aplicación de gestión interna que migramos — dashboard con tablas de datos, filtros y actualizaciones frecuentes — la migración a Zoneless redujo el INP de 380ms a 95ms. La diferencia viene de eliminar los ciclos de detección de cambios innecesarios que Zone.js disparaba con cada interacción del usuario, aunque ningún dato de la vista hubiera cambiado.


    Artículo anterior: Angular 21: el cambio de paradigma que no podés ignorar | Serie Angular 20 → 21.2 | Próximo: Signal Forms: cuando los formularios reactivos finalmente tienen sentido →

Tags

tsql (27)mssql (26)sql (20)devops (20)dotnet (18)docker (15)performance (14)contenedores (11)dotnet10 (10)linux (9)csharp (8)microservicios (7)angular (7)angular21 (7)sql server (6)issabel (6)docker-compose (6)typescript (6)mysql (5).NET (5)