Angular Standalone Components: Guia Completo de Migração e Boas Práticas em 2026

Guia completo sobre Angular Standalone Components com migração passo a passo via CLI, roteamento com loadComponent, eliminação de NgModules, testes e ganhos de performance. Exemplos práticos com código.

Angular Standalone Components Migration

Os Standalone Components representam a mudança arquitetural mais significativa do Angular desde a introdução do Ivy. Com a depreciação oficial dos NgModules no Angular 19 e a remoção prevista para versões futuras, a migração para componentes standalone deixou de ser uma opção e tornou-se uma necessidade concreta para qualquer projeto Angular em produção. Essa transformação elimina uma das camadas de abstração mais criticadas do framework — o sistema de módulos — e substitui por um modelo onde cada componente declara explicitamente suas próprias dependências. O resultado é um código mais enxuto, bundles menores e uma experiência de desenvolvimento significativamente mais fluida.

A adoção de standalone components simplifica o modelo mental necessário para trabalhar com Angular. Em vez de rastrear quais módulos importam quais declarações, cada componente funciona como uma unidade independente. Essa abordagem se alinha com a direção adotada por outros frameworks modernos e resolve problemas históricos de tree-shaking que os NgModules introduziam ao agrupar declarações em blocos monolíticos.

Migração Automatizada

O Angular CLI oferece um schematic oficial (ng g @angular/core:standalone) que automatiza a conversão de projetos inteiros para standalone components. Em benchmarks reais, projetos migrados apresentam redução média de 55% no bundle inicial graças ao tree-shaking granular que componentes standalone possibilitam.

NgModules vs Standalone Components: Comparação Direta

Para compreender a magnitude da mudança, vale examinar como um componente é estruturado no modelo standalone em comparação com o modelo tradicional baseado em NgModules.

No modelo anterior, cada componente precisava ser declarado dentro de um NgModule. Esse módulo era responsável por importar outros módulos que forneciam as dependências necessárias — pipes, diretivas e outros componentes. Essa indireção significava que um componente simples que utilizava *ngFor e um pipe customizado dependia de toda a cadeia de importações do módulo pai.

No modelo standalone, o componente declara diretamente tudo o que precisa:

hero-list.component.tstypescript
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HeroCardComponent } from './hero-card.component';
import { SearchPipe } from '../pipes/search.pipe';

@Component({
  selector: 'app-hero-list',
  standalone: true,
  imports: [CommonModule, 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('');
}

A propriedade standalone: true no decorator @Component indica que o componente gerencia suas próprias dependências. O array imports lista exatamente o que o componente utiliza — nenhum módulo intermediário é necessário. Essa transparência facilita a análise estática do compilador, permitindo que o bundler elimine com precisão o código não utilizado.

A diferença prática é substancial. Com NgModules, adicionar um novo pipe a um componente exigia localizar o módulo correto, importar o módulo que continha o pipe e garantir que não houvesse conflitos de nomes. Com standalone components, basta adicionar o import diretamente no componente que o utiliza.

O Processo de Migração em 3 Etapas via CLI

O Angular CLI fornece um schematic dedicado que automatiza a migração de projetos existentes para standalone components. O processo é dividido em três etapas sequenciais, cada uma abordando um aspecto específico da conversão.

Etapa 1: Converter Todas as Declarações para Standalone

A primeira execução do schematic analisa todos os componentes, diretivas e pipes declarados em NgModules e adiciona a flag standalone: true a cada um, movendo as dependências necessárias para o array imports de cada declaração:

bash
# Step 1: Convert all declarations to standalone
ng g @angular/core:standalone --path=src/app

Esse comando percorre recursivamente o diretório especificado, identifica todas as declarações em NgModules e as converte para standalone. O schematic é inteligente o suficiente para resolver as dependências transitivas — se um componente dependia de um módulo que exportava CommonModule, o schematic adiciona CommonModule diretamente ao array imports do componente.

Etapa 2: Remover NgModules Vazios

Após a conversão das declarações, muitos NgModules ficam vazios ou contêm apenas importações de outros módulos. A segunda execução remove esses módulos redundantes:

bash
# Step 2: Remove empty NgModules
ng g @angular/core:standalone --path=src/app

O schematic identifica NgModules que não possuem mais declarações e cujas importações foram redistribuídas para os componentes standalone. Esses módulos são removidos do projeto, junto com quaisquer referências a eles em outros arquivos.

Etapa 3: Atualizar o Bootstrap da Aplicação

A etapa final converte o bootstrap da aplicação do modelo baseado em platformBrowserDynamic().bootstrapModule() para bootstrapApplication(). O arquivo main.ts passa a referenciar diretamente o componente raiz e um objeto de configuração:

main.ts (after migration)typescript
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));

O objeto appConfig centraliza os providers globais da aplicação — roteamento, HTTP client, interceptors e quaisquer outros serviços que precisem estar disponíveis em toda a árvore de componentes:

app.config.tstypescript
import { ApplicationConfig } 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: [
    provideRouter(routes),
    provideHttpClient(withInterceptors([authInterceptor]))
  ]
};

Esse modelo baseado em funções (provideRouter, provideHttpClient) substitui as importações de módulos (RouterModule.forRoot(), HttpClientModule) e oferece melhor tree-shaking, pois cada função provider pode ser analisada individualmente pelo bundler.

Migração de Roteamento com loadComponent e loadChildren

O roteamento é uma das áreas que mais se beneficia da migração para standalone components. O padrão anterior de lazy loading carregava módulos inteiros, incluindo todas as suas declarações, mesmo quando apenas um componente era necessário para a rota.

O modelo baseado em NgModules utilizava loadChildren apontando para um módulo:

app.routes.ts (before)typescript
const routes: Routes = [
  {
    path: 'dashboard',
    loadChildren: () => import('./dashboard/dashboard.module')
      .then(m => m.DashboardModule)
  }
];

No modelo standalone, loadComponent carrega diretamente o componente necessário, e loadChildren pode apontar para um arquivo de rotas em vez de um módulo:

app.routes.ts (after)typescript
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)
  }
];

Para rotas com sub-rotas, o arquivo de rotas filhas exporta um array de Routes em vez de um módulo:

settings/settings.routes.tstypescript
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)
      }
    ]
  }
];

Essa abordagem granular garante que cada rota carregue exclusivamente o código necessário para renderizar aquela tela específica. O impacto no tamanho dos chunks lazy-loaded é dramático — em vez de carregar um módulo inteiro com dezenas de componentes, apenas o componente da rota e suas dependências diretas são transferidos.

Pronto para mandar bem nas entrevistas de Angular?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Tratamento de SharedModules na Migração

A maioria dos projetos Angular possui um SharedModule que agrupa componentes, diretivas e pipes reutilizáveis. Esse padrão funcionava como um ponto central de exportação, mas conflitava diretamente com o tree-shaking eficiente, pois importar o SharedModule em qualquer lugar significava incluir todas as suas declarações no bundle.

A migração para standalone elimina completamente essa necessidade:

typescript
// 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 {}

Com standalone components, cada declaração compartilhada é importada individualmente por quem a utiliza. Se um componente precisa apenas do LoadingSpinner, ele importa somente esse componente — sem carregar o TooltipDirective ou o TruncatePipe que não utiliza. Essa mudança transforma o tree-shaking de "nível de módulo" para "nível de componente", resultando em bundles significativamente menores.

Para organização do código, os componentes compartilhados podem continuar residindo em um diretório shared/, mas cada arquivo exporta seu próprio componente standalone. Um arquivo de barrel export (index.ts) facilita as importações sem reintroduzir o acoplamento do SharedModule.

Forçando Desenvolvimento Exclusivamente Standalone

Para prevenir a criação acidental de novos NgModules em projetos já migrados, o Angular Compiler oferece uma opção de configuração que rejeita qualquer componente não-standalone durante a compilação:

tsconfig.jsonjson
{
  "angularCompilerOptions": {
    "strictStandalone": true
  }
}

Com essa flag ativada, qualquer tentativa de criar um componente sem standalone: true ou de declará-lo em um NgModule resulta em erro de compilação. Essa proteção é especialmente valiosa em equipes grandes, onde novos membros podem inadvertidamente seguir padrões antigos encontrados em tutoriais desatualizados.

Além da configuração do compilador, recomenda-se adicionar uma regra de linting customizada que detecte e reporte NgModules no código-fonte, criando uma camada adicional de proteção contra regressões. O Angular CLI na versão atual já gera todos os novos componentes como standalone por padrão, exigindo a flag --standalone=false explicitamente para o comportamento legado.

Ganhos de Performance com Standalone Components

Os benefícios de performance da migração para standalone components são mensuráveis e consistentes em diferentes escalas de projeto. A eliminação dos NgModules permite que o bundler (esbuild no Angular 19+) analise as dependências com granularidade de componente em vez de granularidade de módulo.

Os dados a seguir refletem benchmarks realizados em um projeto corporativo de médio porte (~200 componentes, ~30 rotas lazy-loaded):

| Metric | NgModule-based | Standalone | Improvement | |--------|---------------|------------|-------------| | Initial bundle | 485 KB | 218 KB | -55% | | Largest lazy chunk | 142 KB | 38 KB | -73% | | Time to Interactive | 3.2s | 1.8s | -44% | | Build time (esbuild) | 12.4s | 8.1s | -35% |

A redução de 55% no bundle inicial decorre diretamente do tree-shaking aprimorado. Com NgModules, o bundler não conseguia determinar com precisão quais declarações de um módulo eram efetivamente utilizadas, incluindo todas por segurança. Com standalone components, cada import é explícito e rastreável, permitindo a eliminação cirúrgica de código morto.

O tempo de build também se beneficia, pois o compilador processa componentes individuais em paralelo, sem a necessidade de resolver a árvore de dependências de módulos interconectados. Em projetos com centenas de componentes, essa paralelização resulta em ganhos expressivos no ciclo de desenvolvimento.

Testes de Standalone Components

Uma vantagem frequentemente subestimada dos standalone components é a simplificação dos testes unitários. Com NgModules, configurar o TestBed exigia importar o módulo que continha o componente sob teste, além de mockar todas as dependências transitivas que o módulo trazia. Com standalone components, o setup de teste é direto:

hero-list.component.spec.tstypescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HeroListComponent } from './hero-list.component';
import { HeroService } from '../services/hero.service';

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);
  });
});

O componente standalone é importado diretamente no imports do TestBed, em vez de ser declarado indiretamente via um módulo. Isso torna o teste verdadeiramente unitário — as dependências do componente são exatamente aquelas declaradas em seu array imports, sem surpresas de dependências transitivas vindas de módulos.

A propriedade componentRef.setInput() permite definir inputs programaticamente, substituindo a necessidade de criar um componente host para testes. Essa API se integra naturalmente com o modelo standalone e simplifica cenários de teste que anteriormente exigiam wrappers auxiliares.

Armadilhas Comuns na Migração

Apesar da automação fornecida pelo CLI, existem cenários que exigem atenção manual durante a migração.

O primeiro ponto de atenção envolve imports circulares. Quando dois componentes standalone se importam mutuamente, o compilador emite um erro. A solução envolve extrair a lógica compartilhada para um terceiro componente ou utilizar injeção de dependência para quebrar o ciclo.

O segundo cenário comum são providers com escopo de módulo. NgModules permitiam fornecer serviços com escopo limitado ao módulo. Com standalone components, o equivalente é utilizar providers no decorator @Component para escopo de componente, ou provideIn: 'root' para serviços singleton globais. Para casos intermediários, a função provideRouter com withComponentInputBinding() e o uso de ENVIRONMENT_INITIALIZER oferecem controle granular.

O terceiro problema frequente é a migração parcial. Projetos que migram apenas parte dos componentes para standalone acabam com um modelo híbrido onde NgModules importam componentes standalone e vice-versa. Embora o Angular suporte essa interoperabilidade, ela adiciona complexidade cognitiva e reduz os ganhos de tree-shaking. A recomendação é realizar a migração completa em uma única iteração, utilizando o schematic do CLI para automatizar o máximo possível.

Por fim, bibliotecas de terceiros que ainda exportam NgModules podem ser utilizadas normalmente em componentes standalone — basta importar o módulo no array imports do componente. O Angular mantém retrocompatibilidade total nesse aspecto, garantindo que a migração não quebre dependências externas.

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

Conclusão

A migração para Angular Standalone Components representa uma evolução fundamental na arquitetura de aplicações Angular. Com a depreciação dos NgModules e as ferramentas automatizadas disponíveis no CLI, o processo de migração se tornou acessível mesmo para projetos de grande porte. Os principais pontos abordados neste guia incluem:

  • Standalone components eliminam NgModules ao declarar dependências diretamente no decorator @Component, simplificando o modelo mental e melhorando o tree-shaking
  • O schematic do Angular CLI (ng g @angular/core:standalone) automatiza a migração em três etapas: conversão de declarações, remoção de módulos vazios e atualização do bootstrap
  • O roteamento migrado utiliza loadComponent para lazy loading granular de componentes individuais e loadChildren para arquivos de rotas
  • SharedModules são substituídos por importações diretas de componentes standalone, permitindo tree-shaking no nível de componente
  • A flag strictStandalone no tsconfig.json previne a criação acidental de componentes não-standalone
  • Benchmarks demonstram reduções de 55% no bundle inicial e 73% nos chunks lazy-loaded em projetos reais
  • Testes unitários se tornam mais simples e verdadeiramente isolados, importando o componente diretamente no TestBed
  • Imports circulares, providers com escopo de módulo e migração parcial são as armadilhas mais comuns que exigem atenção manual

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

Tags

#angular
#standalone-components
#migration
#tutorial

Compartilhar

Artigos relacionados