Angular 21 consolida el modelo de componentes standalone como el estándar absoluto del framework. Si vienes de versiones anteriores que usaban NgModules, o si estás empezando con Angular ahora mismo, esta guía te explica todo lo que necesitas saber sobre cómo funciona la arquitectura moderna de Angular.

¿Qué son los componentes standalone?

Los componentes standalone son componentes que no dependen de un NgModule para funcionar. Se gestionan a sí mismos: declaran sus propias dependencias directamente. En Angular 21, todos los proyectos nuevos usan standalone por defecto y NgModules ha quedado como una forma legacy.

Crear un proyecto Angular 21

# Instalar Angular CLI globalmente
npm install -g @angular/cli

# Crear nuevo proyecto (standalone por defecto)
ng new mi-proyecto --standalone

# Entrar al directorio e iniciar el servidor
cd mi-proyecto
ng serve

Anatomía de un componente standalone

import { Component, input, output, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterLink } from '@angular/router';

@Component({
  selector: 'app-tarjeta-producto',
  standalone: true,
  imports: [CommonModule, RouterLink],  // Dependencias del componente
  template: `
    <div class="tarjeta" [class.destacada]="producto().destacado">
      <h3>{{ producto().nombre }}</h3>
      <p>{{ producto().descripcion }}</p>
      <span class="precio">{{ producto().precio | currency:'EUR' }}</span>
      
      <button (click)="agregarAlCarrito()">
        Añadir al carrito
      </button>
      
      <a [routerLink]="['/producto', producto().id]">Ver más</a>
    </div>
  `,
})
export class TarjetaProductoComponent {
  // Input como signal (Angular 17+)
  producto = input.required<Producto>();

  // Output moderno
  carritoActualizado = output<Producto>();

  // Estado local con signals
  añadido = signal(false);

  agregarAlCarrito() {
    this.añadido.set(true);
    this.carritoActualizado.emit(this.producto());
  }
}

Signals: la revolución del estado en Angular

Angular Signals, introducidos en Angular 16 y maduros en Angular 21, son la forma moderna de gestionar el estado reactivo. Reemplazan gradualmente a RxJS para casos de uso de estado local y hacen que la detección de cambios sea mucho más eficiente.

import { Component, signal, computed, effect } from '@angular/core';

@Component({
  selector: 'app-contador',
  standalone: true,
  template: `
    <p>Conteo: {{ count() }}</p>
    <p>Doble: {{ doble() }}</p>
    <button (click)="incrementar()">+</button>
    <button (click)="decrementar()">-</button>
  `
})
export class ContadorComponent {
  count = signal(0);

  // computed: se actualiza automáticamente cuando count cambia
  doble = computed(() => this.count() * 2);

  constructor() {
    // effect: ejecuta código cuando las signals cambian
    effect(() => {
      console.log('El conteo cambió a:', this.count());
    });
  }

  incrementar() { this.count.update(v => v + 1); }
  decrementar() { this.count.update(v => v - 1); }
}

Inyección de dependencias sin constructor

Angular 21 permite inyectar servicios de forma más limpia usando la función inject(), sin necesidad de declararlos en el constructor:

import { Component, inject, signal, OnInit } from '@angular/core';
import { ProductoService } from './producto.service';
import { toSignal } from '@angular/core/rxjs-interop';

@Component({
  selector: 'app-lista-productos',
  standalone: true,
  template: `
    @if (cargando()) {
      <p>Cargando productos...</p>
    } @else {
      @for (producto of productos(); track producto.id) {
        <app-tarjeta-producto [producto]="producto" />
      } @empty {
        <p>No hay productos disponibles.</p>
      }
    }
  `
})
export class ListaProductosComponent implements OnInit {
  private productoService = inject(ProductoService);

  cargando = signal(true);
  productos = signal<Producto[]>([]);

  ngOnInit() {
    this.productoService.obtenerTodos().subscribe({
      next: (data) => {
        this.productos.set(data);
        this.cargando.set(false);
      },
      error: () => this.cargando.set(false)
    });
  }
}

Nueva sintaxis de plantillas: @if, @for, @switch

Angular 17+ introdujo una nueva sintaxis de flujo de control que reemplaza a las directivas *ngIf, *ngFor y *ngSwitch. Es más legible y tiene mejor rendimiento.

<!-- Antes (ngIf) -->
<div *ngIf="usuario; else sinUsuario">
  <p>Hola, {{ usuario.nombre }}</p>
</div>
<ng-template #sinUsuario>
  <p>No hay usuario</p>
</ng-template>

<!-- Ahora (@if) - mucho más claro -->
@if (usuario()) {
  <p>Hola, {{ usuario()!.nombre }}</p>
} @else {
  <p>No hay usuario</p>
}

<!-- Antes (ngFor) -->
<li *ngFor="let item of items; trackBy: trackById">{{ item.nombre }}</li>

<!-- Ahora (@for) - con track obligatorio y @empty incluido -->
@for (item of items(); track item.id) {
  <li>{{ item.nombre }}</li>
} @empty {
  <li>No hay elementos</li>
}

Routing con componentes standalone

// app.routes.ts
import { Routes } from '@angular/router';

export const routes: Routes = [
  {
    path: '',
    loadComponent: () =>
      import('./pages/home/home.component').then(m => m.HomeComponent)
  },
  {
    path: 'productos',
    loadComponent: () =>
      import('./pages/productos/productos.component').then(m => m.ProductosComponent)
  },
  {
    path: 'producto/:id',
    loadComponent: () =>
      import('./pages/detalle/detalle.component').then(m => m.DetalleComponent)
  },
  {
    path: '**',
    loadComponent: () =>
      import('./pages/not-found/not-found.component').then(m => m.NotFoundComponent)
  }
];

// main.ts - Bootstrap sin módulos
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { AppComponent } from './app/app.component';
import { routes } from './app/app.routes';

bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes),
  ]
});

Angular 21 es más limpio, más rápido y más fácil de aprender que nunca. Los componentes standalone, los signals y la nueva sintaxis de plantillas hacen que el código sea más expresivo y mantenible. Si aún no has migrado tu forma de pensar Angular al modelo moderno, ahora es el momento.