Angular 20 standalone: o que muda de verdade quando você aposenta os NgModules
Angular 20 vem mantendo sua revolução com componentes standalone, permitindo aposentar de vez os NgModules. Vamos entender o que muda na prática em formulários, lazy loading, providers e SSR, com ganhos reais em clareza, bundle e desenvolvimento.

Começando do básico: você aprendeu Angular na época do app.module.ts, onde tudo dependia de NgModules: declaração de componentes, registro de services, rotas, enfim, um modelo quase burocrático. Agora, em Angular 20, os standalone components dominam o cenário (existentes desde o Angular 15) e prometem simplificar bastante, mas isso também exige uma mudança mental importante.
Antes: formulário tradicional com NgModules
Imagine um componente de formulário de cadastro simples. No paradigma clássico, teríamos algo assim:
// cadastro-form.component.ts
@Component({
selector: 'app-cadastro-form',
templateUrl: './cadastro-form.component.html'
})
export class CadastroFormComponent {
nome = '';
email = '';
salvar() {
console.log('Formulário salvo:', this.nome, this.email);
}
}
E este componente seria declarado em um módulo:
@NgModule({
declarations: [CadastroFormComponent],
imports: [CommonModule, FormsModule],
exports: [CadastroFormComponent]
})
export class CadastroModule {}
Para usar esse form em alguma rota, seria comum configurar lazy loading com o módulo inteiro:
const routes: Routes = [
{
path: 'cadastro',
loadChildren: () =>
import('./cadastro/cadastro.module').then(m => m.CadastroModule),
},
];
O problema? Você precisava pedir licença ao Angular para que ele organizasse esse quebra-cabeça de NgModules, e a árvore de dependências ficava espalhada — módulos declarando componentes, módulos importando outros, providers escondidos longe dos componentes que os usam.
Depois: migrando para standalone e signals em Angular 20
Em Angular 20, o padrão é criar componentes standalone, que entendem por si só suas dependências e facilitam o lazy loading independente de NgModules.
Aqui está o mesmo formulário, agora standalone, e usando signal para reatividade:
import { Component, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-cadastro-form',
standalone: true,
imports: [CommonModule, FormsModule],
template: `
<form (ngSubmit)="salvar()">
<label>
Nome:
<input [(ngModel)]="nome()"/>
</label>
<label>
Email:
<input [(ngModel)]="email()"/>
</label>
<button type="submit">Salvar</button>
</form>
`
})
export class CadastroFormComponent {
nome = signal('');
email = signal('');
salvar() {
console.log('Formulário salvo:', this.nome(), this.email());
}
}
E para a rota com lazy loading, nada de loadChildren com módulos. Basta importar o componente standalone diretamente:
import { Routes } from '@angular/router';
import { CadastroFormComponent } from './cadastro-form.component';
export const routes: Routes = [
{
path: 'cadastro',
loadComponent: () =>
import('./cadastro-form.component').then(c => c.CadastroFormComponent)
}
];
Aqui, percebemos dois ganhos práticos:
- Bundle menor: sem precisar arrastar módulos inteiros, seu carregamento fica mais granular.
- Clareza total: o componente entende o que importa, sem dependências ocultas em NgModules.
Providers e injeção: o que mudou?
Um ponto que quase ninguém avisa quando você migra para standalone é a mudança na forma de injetar serviços e declarar providers.
Onde os providers vivem?
Antes, providers eram geralmente declarados em módulos ou no @Injectable({providedIn: 'root'}). Como os módulos deixaram de ser obrigatórios, o providers dentro de componentes standalone passou a ser a forma oficial e principal de declarar provedores locais em níveis hierárquicos e sub-árvores da aplicação, sem a necessidade de um arquivo de módulo para envelopá-los:
@Component({
standalone: true,
providers: [MeuServico],
// ...
})
export class MeuComponente {}
Isso delimita o escopo do service ao componente e seus descendentes, muitas vezes uma vantagem para evitar singletons desnecessários.
Usando inject() ao invés de construtor
Angular 20 encoraja o uso da função inject() para injeção:
import { inject } from '@angular/core';
@Component({
standalone: true,
providers: [MeuServico],
// ...
})
export class MeuComponente {
private meuServico = inject(MeuServico);
// ...
}
Vantagem: não precisa declarar o service no construtor, o que deixa o código mais enxuto e facilita utilização em variáveis ou propriedades fora do contexto de classe (como em signals ou funções standalone).
Server-side rendering (SSR): o que muda?
Se você trabalha com SSR, é importante notar que Angular 20 com standalone mantém compatibilidade, mas exige alguns cuidados.
Rotas e SSR
Quando usar loadComponent para lazy loading de componentes standalone, o Angular Universal saberá resolver sem o antigo esquema do loadChildren. O SSR continua, porém o comportamento padrão de pré-renderização e revalidação se mantém.
Providers Scoped no SSR
Se você move providers para o nível do componente, tenha em mente que o ciclo de vida deles nos requests SSR passa a ser mais curto. Para serviços que precisam durar toda a requisição (ex.: contextos de usuário), opte por providers no nível do root (como providedIn: 'root').
Armadilhas comuns e pontos a observar
- Não é só decoração: migrar para standalone não significa só mudar a flag, você deve garantir importar todos dependências explícitas em
importsdo componente. - Tratamento de rotas complexas: para rotas filhas e módulos grandes, às vezes usar módulos ainda é vantajoso pela organização.
- SSR e efeitos colaterais: evite inicialização com efeitos colaterais no construtor, prefira
inject()oungOnInit. - Signals não são magia: prefira signals para estados locais simples, mas gerenciadores mais robustos ainda podem precisar de RxJS em apps complexos.
Resumo dos ganhos e desafios
| Aspecto | Angular com NgModules | Angular 20 standalone |
|---|---|---|
| Estrutura | Muita burocracia com módulos | Componentes autocontidos |
| Lazy loading | Modulos pesados | Lazy por componente (loadComponent) |
| Declaração de providers | No módulo ou root | Em componente com providers ou root |
| Injeção de dependências | Construtor | Função inject() |
| Bundle e carregamento | Maior | Mais enxuto e granular |
| SSR | Compatível | Requer atenção a escopo de providers |
Próximos passos
Se você está voltando para Angular ou iniciando migração para standalone:
- Experimente converter um módulo pequeno para standalone e meça impacto no bundle.
- Utilize signals para estado simples e observe a melhora na performance.
- No SSR, teste cuidadosamente providers escopados.
- Acompanhe a documentação oficial do Angular 17 para detalhes práticos.
Angular 20 não é apenas uma evolução técnica, mas um convite para pensarmos em aplicações cada vez mais claras, modulares e performáticas. Adaptar a mentalidade de NgModules para componentes standalone vai trazer muito ganho, basta explorar com calma e paciência.