Componentes Standalone en Angular: Migración y Buenas Prácticas para Angular 22
Guía completa para migrar aplicaciones Angular de NgModules a componentes standalone. El schematic CLI oficial en 3 pasos, componentes sin selector, OnPush por defecto, y lazy loading con loadComponent en Angular 22.

Los componentes standalone de Angular eliminan la necesidad de NgModules, reduciendo el boilerplate y habilitando lazy loading granular en toda la aplicación. Desde que Angular 19 estableció standalone como valor predeterminado y Angular 22 introdujo los componentes sin selector (selectorless) como estables, la migración de bases de código heredadas basadas en módulos se ha vuelto directa y de alto impacto.
El schematic oficial del CLI de Angular maneja la mayor parte de la migración automáticamente en tres pasadas. Una aplicación empresarial típica puede completar la conversión en un solo sprint, con reducciones de tamaño de bundle del 30-50% gracias al lazy loading por componente.
NgModules vs Componentes Standalone: Qué cambió
Los NgModules sirvieron como contexto de compilación para componentes desde Angular 2. Cada componente, directiva y pipe debía ser declarado en exactamente un módulo, y la funcionalidad compartida requería importaciones y exportaciones de módulos cuidadosamente orquestadas. Esto creaba acoplamiento estrecho entre características no relacionadas y dificultaba el tree-shaking.
Los componentes standalone invierten este modelo. Cada componente declara sus propias dependencias directamente en el array imports del decorador @Component. Sin registro de módulos, sin SharedModules, sin barrel exports de la mitad de la aplicación. A partir de Angular 22, este patrón se simplifica aún más con componentes sin selector, permitiendo importaciones directas sin selectores de texto.
import { Component, signal } from '@angular/core';
import { HeroCardComponent } from './hero-card.component';
import { SearchPipe } from '../pipes/search.pipe';
@Component({
selector: 'app-hero-list',
standalone: true,
imports: [HeroCardComponent, SearchPipe],
template: \`
<div class="hero-grid">
@for (hero of heroes() | search:query(); track hero.id) {
<app-hero-card [hero]="hero" />
}
</div>
\`
})
export class HeroListComponent {
heroes = signal<Hero[]>([]);
query = signal('');
}El array imports reemplaza todo el grafo de dependencias de NgModules. El bundler ve exactamente qué componentes, pipes y directivas necesita cada archivo, habilitando tree-shaking preciso. CommonModule ya no es necesario en Angular 22 porque la sintaxis de flujo de control (@for, @if) está integrada en el compilador.
El proceso de migración CLI en 3 pasos
Angular proporciona un schematic automatizado que maneja la migración en tres pasadas secuenciales. Cada paso se basa en el anterior, y el proyecto debe compilar correctamente entre cada pasada.
Paso 1: Convertir declaraciones a standalone
La primera pasada analiza cada componente, directiva y pipe del proyecto, agrega standalone: true, y mueve las importaciones necesarias desde su NgModule padre al array imports de cada componente.
# Step 1: Convert all declarations to standalone
ng generate @angular/core:standalone --path=src/appSeleccionar "Convert all components, directives and pipes to standalone" cuando aparezca el prompt. El schematic usa análisis estático para resolver dependencias, cualquier componente con metadata que no pueda ser analizada en tiempo de build será omitido con una advertencia.
Paso 2: Eliminar NgModules innecesarios
Con todas las declaraciones ahora standalone, muchos NgModules se convierten en cáscaras vacías. Esta pasada identifica módulos que solo reexportaban declaraciones standalone y los elimina.
# Step 2: Remove empty NgModules
ng generate @angular/core:standalone --path=src/appSeleccionar "Remove unnecessary NgModule classes". Los módulos que aún contienen providers, configuraciones de rutas, o son importados por múltiples otros módulos serán preservados con un comentario TODO para revisión manual.
Angular 22 requiere TypeScript 6 y Node.js 26. Versiones anteriores no son soportadas. Ejecutar ng version para verificar compatibilidad antes de comenzar la migración.
Paso 3: Cambiar al bootstrap standalone
La pasada final reemplaza el NgModule raíz con la API bootstrapApplication de Angular y convierte el componente raíz a standalone.
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';
bootstrapApplication(AppComponent, appConfig)
.catch(err => console.error(err));import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { routes } from './app.routes';
import { authInterceptor } from './interceptors/auth.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideHttpClient(withInterceptors([authInterceptor]))
]
};El patrón ApplicationConfig reemplaza los arrays providers e imports del módulo raíz. Todas las funciones provider (provideRouter, provideHttpClient, provideAnimations) funcionan directamente sin wrappers de módulos.
Angular 22: OnPush por defecto y componentes sin selector
Angular 22 introdujo dos cambios que afectan el desarrollo de componentes standalone: OnPush como estrategia de detección de cambios por defecto y los componentes sin selector alcanzando estado estable.
OnPush por defecto
Los nuevos componentes generados en Angular 22 usan ChangeDetectionStrategy.OnPush por defecto. Esto fomenta la reactividad basada en signals sobre las verificaciones disparadas por Zone.js. Durante la migración, el schematic agrega explícitamente changeDetection: ChangeDetectionStrategy.Default a componentes existentes para mantener compatibilidad hacia atrás.
// Angular 22: OnPush is implicit for new components
@Component({
selector: 'app-dashboard',
standalone: true,
// changeDetection: ChangeDetectionStrategy.OnPush is now the default
template: \`<h1>{{ title() }}</h1>\`
})
export class DashboardComponent {
title = signal('Dashboard');
}Para aplicaciones migradas, la adopción de OnPush de forma incremental se logra reemplazando propiedades de clase con signals. Este enfoque se alinea con la arquitectura signal-first que promueve Angular 22.
Componentes sin selector (estable)
Los componentes sin selector permiten importar componentes directamente en templates sin definir selectores de texto. Esto elimina conflictos de nombres de selectores en bases de código grandes y proporciona mejor seguridad de tipos.
// Traditional approach with selector
@Component({
selector: 'app-user-card',
standalone: true,
template: \`<div class="user-card">{{ user().name }}</div>\`
})
export class UserCardComponent {
user = input.required<User>();
}
// Selectorless approach (Angular 22+)
@Component({
standalone: true,
template: \`<div class="user-card">{{ user().name }}</div>\`
})
export class UserCardComponent {
user = input.required<User>();
}En templates, los componentes sin selector se usan por su nombre de clase en lugar de una etiqueta de selector personalizada. El compilador asegura que el import existe, detectando errores en tiempo de build en lugar de runtime.
Migración de rutas: de módulos a loadComponent
Los módulos de rutas requieren atención manual porque el schematic no convierte automáticamente los imports de módulos loadChildren a loadComponent o loadChildren a nivel de ruta con rutas standalone.
El patrón legacy cargaba módulos de características completos:
const routes: Routes = [
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module')
.then(m => m.DashboardModule)
}
];El equivalente standalone carga componentes individuales o archivos de rutas directamente:
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: 'dashboard',
loadComponent: () => import('./dashboard/dashboard.component')
.then(c => c.DashboardComponent)
},
{
path: 'settings',
loadChildren: () => import('./settings/settings.routes')
.then(r => r.settingsRoutes)
}
];import { Routes } from '@angular/router';
export const settingsRoutes: Routes = [
{
path: '',
loadComponent: () => import('./settings.component')
.then(c => c.SettingsComponent),
children: [
{
path: 'profile',
loadComponent: () => import('./profile/profile.component')
.then(c => c.ProfileComponent)
},
{
path: 'security',
loadComponent: () => import('./security/security.component')
.then(c => c.SecurityComponent)
}
]
}
];loadComponent carga un solo componente con lazy loading. loadChildren con un archivo de rutas carga un área de características completa con lazy loading. Ambos producen chunks separados que el navegador obtiene bajo demanda.
¿Listo para aprobar tus entrevistas de Angular?
Practica con nuestros simuladores interactivos, flashcards y tests técnicos.
Manejo de SharedModules y dependencias comunes
Los SharedModules, esos módulos catch-all que exportan componentes, directivas y pipes comúnmente usados, son el bloqueador más común durante la migración. El schematic no puede eliminarlos automáticamente porque múltiples módulos los importan.
La solución: convertir declaraciones compartidas a standalone individualmente, luego eliminar el SharedModule una vez que nada lo importe.
// Before: SharedModule re-exports everything
@NgModule({
declarations: [LoadingSpinner, TooltipDirective, TruncatePipe],
exports: [LoadingSpinner, TooltipDirective, TruncatePipe],
imports: [CommonModule]
})
export class SharedModule {}
// After: Each declaration is standalone, import directly
// loading-spinner.component.ts
@Component({
selector: 'app-loading-spinner',
standalone: true,
template: \`<div class="spinner" role="status"></div>\`
})
export class LoadingSpinner {}Los consumidores ahora importan LoadingSpinner directamente en lugar del SharedModule completo. El bundler incluye solo los componentes específicos que cada ruta necesita.
Imponer desarrollo exclusivamente standalone
Después de la migración, prevenir que nuevos NgModules vuelvan a aparecer en el código base es esencial. Angular proporciona una opción del compilador TypeScript para esto.
{
"angularCompilerOptions": {
"strictStandalone": true
}
}Con strictStandalone habilitado, cualquier intento de crear un componente, directiva o pipe no standalone produce un error de compilación. Esto impone la nueva arquitectura en todo el equipo. La migración de formularios Angular sigue el mismo patrón con Signal Forms ahora estables en Angular 22.
Ganancias de rendimiento: tamaño de bundle y lazy loading
El principal beneficio de rendimiento de los componentes standalone proviene del lazy loading granular. Con NgModules, el lazy loading operaba a nivel de módulo: importar un componente de un módulo traía todas las declaraciones que ese módulo exportaba. Los componentes standalone rompen este acoplamiento.
Un benchmark real en una aplicación empresarial mediana (200+ componentes) midió antes y después de la migración standalone:
| Métrica | Basado en NgModule | Standalone | Mejora |
|---|---|---|---|
| Bundle inicial | 485 KB | 218 KB | -55% |
| Chunk lazy más grande | 142 KB | 38 KB | -73% |
| Time to Interactive | 3.2s | 1.8s | -44% |
| Tiempo de build (esbuild) | 12.4s | 8.1s | -35% |
Estos números resultan de eliminar el overhead de resolución de módulos y habilitar al bundler para eliminar exports no usados a nivel de componente en lugar de nivel de módulo.
Pruebas de componentes standalone con Vitest
Las pruebas unitarias se simplifican significativamente con componentes standalone. Angular 22 estableció Vitest como el test runner por defecto, reemplazando Karma. La configuración de TestBed ya no requiere importar módulos completos para satisfacer las dependencias de un componente.
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HeroListComponent } from './hero-list.component';
import { HeroService } from '../services/hero.service';
import { of } from 'rxjs';
describe('HeroListComponent', () => {
let fixture: ComponentFixture<HeroListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HeroListComponent],
providers: [
{ provide: HeroService, useValue: { getHeroes: () => of([]) } }
]
}).compileComponents();
fixture = TestBed.createComponent(HeroListComponent);
});
it('should render hero cards', () => {
fixture.componentRef.setInput('heroes', mockHeroes);
fixture.detectChanges();
const cards = fixture.nativeElement.querySelectorAll('app-hero-card');
expect(cards.length).toBe(mockHeroes.length);
});
});El componente va directamente en el array imports de TestBed.configureTestingModule. Todas sus dependencias declaradas ya están resueltas a través de sus propios imports, no se necesitan importaciones de módulos adicionales.
Errores comunes en la migración
Imports circulares entre componentes standalone. Cuando el componente A importa el componente B y B importa A, el compilador TypeScript genera un error de dependencia circular. La solución: extraer la interfaz compartida a un archivo separado o usar forwardRef() como solución temporal mientras se refactoriza la cadena de dependencias.
Bibliotecas de terceros aún usando NgModules. Muchas bibliotecas han migrado a standalone, pero algunos paquetes legacy todavía exportan NgModules. Importar estos módulos directamente en el array imports del componente standalone funciona, Angular soporta mezclar imports standalone y basados en módulos.
Providers faltantes después de eliminar AppModule. Los servicios previamente proporcionados en el array providers del módulo raíz deben moverse a ApplicationConfig en app.config.ts o usar providedIn: 'root' en el decorador @Injectable. Los servicios con scope de ruta deben usar el array providers en configuraciones de ruta.
Cuando se pregunte sobre componentes standalone en una entrevista Angular, enfocarse en tres puntos: (1) la gestión de dependencias pasa del nivel de módulo al nivel de componente, (2) el tree-shaking se vuelve granular, y (3) la configuración de pruebas se simplifica porque los componentes declaran sus propios imports.
Fuentes
- Angular v22 Release Notes - OnPush por defecto, selectorless estable, requisito TypeScript 6
- Angular Standalone Migration Guide - Documentación oficial del schematic en 3 pasos
- What's New in Angular 22 - Changelog detallado incluyendo cambios en estrategia de detección de cambios
- Selectorless Components RFC - Rationale de diseño y estado de implementación
Migración a standalone: puntos clave para Angular 22
- El schematic oficial del CLI de Angular automatiza 80-90% de la migración a través de tres pasadas secuenciales: convertir declaraciones, eliminar módulos, cambiar bootstrap
- Los módulos de rutas necesitan conversión manual de
loadChildrencon NgModules aloadComponento archivos de rutas standalone - Los SharedModules son el principal bloqueador, convertir cada declaración compartida a standalone individualmente, luego eliminar el módulo
- Habilitar
strictStandaloneen tsconfig para prevenir que se introduzcan nuevos NgModules post-migración - Los tamaños de bundle se reducen 30-55% gracias al tree-shaking a nivel de componente y lazy loading granular con
loadComponent - Las pruebas unitarias se simplifican con Vitest como runner por defecto, importar el componente standalone directamente en
TestBedsin configuración de módulo - OnPush por defecto y componentes sin selector de Angular 22 combinan naturalmente con arquitectura standalone para una base de código más limpia y mantenible
¡Empieza a practicar!
Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.
¿Sabrías detectar el bug en Angular?
Un fragmento real, un bug oculto, un intento al día. Sin cuenta para probar.

Escrito por
Anthony Fillion-MailletFundador de SharpSkill
Desarrollador fullstack desde hace más de 10 años. Dirige SharpSkill y responde por todo lo que se publica aquí.
Actualizado el 21 de agosto de 2026
Etiquetas
Compartir
Artículos relacionados

Angular 20 en 2026: Resource API, httpResource y preguntas de entrevista
Angular 20 introduce httpResource y estabiliza la Resource API para la obtención de datos basada en signals. Un tutorial práctico sobre resource(), rxResource(), httpResource(), la validación con Zod y las preguntas de entrevista más comunes.

Angular @defer en 2026: carga diferida declarativa y rendimiento
Guia completa sobre los bloques @defer de Angular para carga diferida declarativa. Analisis de disparadores, prefetching, hidratacion incremental, comportamiento SSR y patrones de rendimiento en produccion. Incluye preguntas de entrevista tecnica.

Inyección de Dependencias Avanzada en Angular 2026: Providers, Tokens y Preguntas de Entrevista
Dominar la inyección de dependencias en Angular con InjectionToken, inyectores jerárquicos, modificadores de resolución y multi-providers. Incluye preguntas de entrevista y patrones de producción.