Angular 21: la era de los signals y standalone

Angular 21 marca un punto de inflexion en la historia del framework. Con standalone como comportamiento por defecto, un sistema de signals maduro y mejoras significativas en rendimiento, Angular se posiciona como una opcion moderna y pragmatica para aplicaciones empresariales.

Standalone por defecto

El cambio más visible de Angular 21 es que todos los componentes, directivas y pipes son standalone por defecto. Ya no necesitas declarar standalone: true en el decorador.

// Angular 20 y anteriores
@Component({
  standalone: true, // necesario
  selector: 'app-hello',
  template: `<h1>Hola {{ name() }}</h1>`,
})
export class HelloComponent { }

// Angular 21
@Component({
  selector: 'app-hello',
  template: `<h1>Hola {{ name() }}</h1>`,
})
export class HelloComponent { } // standalone por defecto

Qué pasa con los NgModules?

Los NgModules siguen funcionando pero son considerados legacy. Angular proporciona un schematic de migración:

ng generate @angular/core:standalone-migration

Este comando convierte automaticamente tus componentes NgModule a standalone, actualizando imports y providers.

Sistema de Signals maduro

Angular 21 consolida el sistema de signals con nuevas APIs y mejoras de rendimiento.

Signal Inputs

Los signal inputs reemplazan al decorador @Input() con una API basada en signals:

// Antes: decorador @Input
@Input() title: string = '';
@Input({ required: true }) id!: number;

// Angular 21: signal inputs
title = input<string>('');
id = input.required<number>();

Ventajas:

  • Son signals, por lo que se pueden usar en computed() y effect()
  • Tipado más estricto
  • Mejor integración con OnPush

Signal Outputs

Similar a inputs, los outputs ahora tienen una API funcional:

// Antes
@Output() save = new EventEmitter<FormData>();

// Angular 21
save = output<FormData>();

linkedSignal

linkedSignal es una nueva API que crea un signal que se sincroniza con otro, pero permite escritura local:

const selectedCountry = signal('Bolivia');

// Se actualiza cuando selectedCountry cambia, pero se puede escribir
const selectedCity = linkedSignal(() => {
  const country = selectedCountry();
  return country === 'Bolivia' ? 'Oruro' : 'Desconocida';
});

// Escritura local
selectedCity.set('La Paz'); // funciona

// Pero si selectedCountry cambia, se recalcula
selectedCountry.set('Peru'); // selectedCity vuelve a 'Desconocida'

Signal queries

Las queries del DOM ahora también son signals:

// Antes
@ViewChild('myInput') myInput!: ElementRef;
@ViewChildren(ItemComponent) items!: QueryList<ItemComponent>;

// Angular 21
myInput = viewChild<ElementRef>('myInput');
items = viewChildren(ItemComponent);

// Se pueden usar en computed
isEmpty = computed(() => this.items().length === 0);

Mejoras en hidratacion y SSR

Hidratacion incremental

Angular 21 introduce hidratacion incremental, que permite hidratar componentes bajo demanda en lugar de toda la página de golpe:

@Component({
  selector: 'app-comments',
  template: `
    @defer (hydrate on viewport) {
      <app-comment-list [comments]="comments()" />
    }
  `,
})
export class CommentsComponent {
  comments = input.required<Comment[]>();
}

Los triggers de hidratacion disponibles son:

Trigger Descripcion
hydrate on idle Cuando el navegador esta idle
hydrate on viewport Cuando el elemento entra al viewport
hydrate on interaction Al interactuar (click, focus, etc.)
hydrate on hover Al pasar el cursor
hydrate on timer(ms) Despues de un tiempo
hydrate never Solo contenido estático, sin hidratar

Event replay

Durante la hidratacion, Angular ahora captura y reproduce eventos del usuario. Si un usuario hace click en un boton antes de que se complete la hidratacion, el evento se almacena y se ejecuta cuando el componente este listo.

Control flow estable

El control flow basado en bloques es ahora la única forma recomendada:

<!-- @if reemplaza *ngIf -->
@if (user(); as u) {
  <p>Bienvenido, {{ u.name }}</p>
} @else {
  <p>Inicia sesión</p>
}

<!-- @for reemplaza *ngFor (requiere track) -->
@for (item of items(); track item.id) {
  <app-item [data]="item" />
} @empty {
  <p>No hay elementos</p>
}

<!-- @switch reemplaza ngSwitch -->
@switch (status()) {
  @case ('loading') {
    <app-spinner />
  }
  @case ('error') {
    <app-error [message]="errorMsg()" />
  }
  @default {
    <app-content [data]="data()" />
  }
}

Nuevas APIs de Router

withComponentInputBinding mejorado

Los parametros de ruta se inyectan automaticamente como signal inputs:

// En la configuración de rutas
provideRouter(routes, withComponentInputBinding());

// En el componente - el parametro :id se inyecta automaticamente
@Component({ ... })
export class ProductDetailComponent {
  id = input.required<string>(); // se llena con el parametro :id
}

Functional guards y resolvers

// Guard funcional
export const authGuard: CanActivateFn = () => {
  const auth = inject(AuthService);
  const router = inject(Router);

  if (auth.isAuthenticated()) return true;
  return router.createUrlTree(['/login']);
};

// Resolver funcional
export const productResolver: ResolveFn<Product> = (route) => {
  const service = inject(ProductService);
  return service.getById(route.paramMap.get('id')!);
};

Guia de migración

Paso 1: Actualizar Angular CLI

ng update @angular/core@21 @angular/cli@21

Paso 2: Ejecutar migraciones automaticas

# Migrar a standalone (si aun usas NgModules)
ng generate @angular/core:standalone-migration

# Migrar inputs/outputs a signals
ng generate @angular/core:signal-input-migration
ng generate @angular/core:output-migration

# Migrar control flow
ng generate @angular/core:control-flow-migration

Paso 3: Verificar cambios breaking

  • Eliminar standalone: true de todos los decoradores (ahora es el default)
  • Reemplazar @HostBinding y @HostListener con la propiedad host del decorador
  • Verificar que inject() se usa en lugar de constructor injection

Paso 4: Optimizar para signals

  • Convertir propiedades de componente a signal()
  • Usar computed() para estado derivado
  • Reemplazar Subjects simples con signals donde sea posible
  • Asegurar que todos los componentes usen OnPush

Conclusion

Angular 21 completa la transición hacia un modelo reactivo basado en signals, con standalone por defecto y herramientas de migración maduras. Si vienes de versiones anteriores, las migraciones automaticas facilitan enormemente la actualizacion. Si empiezas un proyecto nuevo, Angular 21 te ofrece una experiencia de desarrollo moderna, tipada y performante desde el primer dia.