Boas práticas no Angular

Boas práticas no Angular: standalone, OnPush+signals, async pipe e pastas por feature para evitar leaks, melhorar performance e facilitar manutenção.

Avatar de Polyana Cunha
Polyana Cunha
Boas práticas no Angular

Angular te deixa produzir muito rápido — e é justamente por isso que dá pra criar uma bagunça enorme sem perceber. Se o seu app já passou de umas 30 telas, você provavelmente já sentiu: change detection rodando à toa, subscribe esquecido segurando memória, formulário com any disfarçado. As boas práticas abaixo não são regra de estilo; são o que separa um projeto que escala de um que trava a cada nova feature.

Imagem de abertura que traduz visualmente a ideia de organização e boas práticas em Angular — estrutura limpa, fluxo de dados previsível, componentes bem separados.
Imagem de abertura que traduz visualmente a ideia de organização e boas práticas em Angular — estrutura limpa, fluxo de dados previsível, componentes bem separados.

Standalone é o padrão agora — pare de arrastar NgModule

Desde o Angular 15 componentes standalone são estáveis, e do 17 em diante são o default do CLI. Se você ainda cria @NgModule pra cada feature, está pagando imposto de boilerplate à toa. Standalone declara as dependências direto no componente, em imports: [], e acabou o vaivém de declarar/exportar em módulo.

O ganho real aparece no lazy loading e no tree-shaking: sem módulos intermediários, o bundler enxerga melhor o que dá pra cortar. Migre gradual — o próprio Angular tem schematic (ng generate @angular/core:standalone) pra converter em etapas. Não precisa reescrever tudo num fim de semana.

Componente burro, componente esperto: divida ou pague depois

Essa é velha mas continua sendo ignorada. Componente esperto (container) sabe de onde vêm os dados: injeta services, conversa com o store, dispara side effects. Componente burro (presentational) só recebe via @Input e emite via @Output — não sabe se o dado veio de API, cache ou mock.

Por que isso importa na prática? Componente burro é trivial de testar (entra input, sai output) e reusável de graça. Quando você mistura fetch de dados com renderização no mesmo lugar, cada teste vira um mock de HttpClient e cada reuso vira copiar-e-colar. A regra simples: se o componente tem constructor cheio de service e template grande, ele quer ser dois.

OnPush + signals: change detection que não te trai

Por padrão o Angular re-checa a árvore inteira a cada evento. Em app pequeno ninguém sente; em app grande é lag na digitação. ChangeDetectionStrategy.OnPush diz ao Angular pra só re-renderizar quando um @Input muda por referência, um evento dispara no componente ou um observable emite via async pipe.

Adote OnPush como padrão, não como otimização de último recurso. E aqui os signals (estáveis desde o Angular 17) casam perfeito: um signal() notifica o change detection de forma granular, sem você depender de imutabilidade manual. computed() deriva estado sem recalcular à toa. A combinação OnPush + signals é hoje o caminho mais previsível — menos markForCheck() espalhado, menos bug de tela que não atualiza.

RxJS sem vazamento: async pipe primeiro, unsubscribe consciente

Subscribe manual é a maior fonte de memory leak em Angular. A regra número um: deixe o async pipe cuidar. Ele assina no template e cancela sozinho quando o componente morre. *ngIf="data$ | async as data" resolve 80% dos casos sem uma linha de unsubscribe.

Quando você precisa assinar no TypeScript — um side effect, um tap que abre modal — não largue solto. Use takeUntilDestroyed() (do @angular/core/rxjs-interop, estável no 16+), que amarra a inscrição ao ciclo de vida do componente sem ngOnDestroy boilerplate. Fuja do .subscribe() seco sem cleanup: ele parece funcionar, e vaza silenciosamente por semanas até o app ficar pesado sem explicação.

Forms tipados e rotas lazy: o que muda de verdade

Desde o Angular 14 os reactive forms são tipados. Se você ainda pega form.value e recebe any, está jogando fora a melhor mudança de DX dos últimos anos. Declare com FormGroup<{ nome: FormControl<string> }> ou deixe o FormBuilder inferir — e o compilador te avisa quando você acessa um campo que não existe ou passa o tipo errado. Formulário é onde bug de tipo mais dói; tipar ali é retorno garantido.

Rotas: carregue tudo que não é a tela inicial com loadComponent / loadChildren. Com standalone, loadComponent: () => import('./pagina').then(m => m.Pagina) já quebra o bundle sem cerimônia. O usuário baixa só o que a rota atual precisa, e o first load agradece.

Estrutura de pastas por feature, não por tipo

Pasta components/, services/, models/ com cinquenta arquivos cada é o clássico que não escala. Você acaba pulando de pasta em pasta pra entender uma única feature. Organize por domínio: features/pedidos/ com o componente, o service, os tipos e o teste juntos. shared/ só pro que é genuinamente reusável, core/ pros singletons de app (guards, interceptors).

O teste é simples: pra deletar uma feature, você deveria conseguir apagar uma pasta. Se apagar exige caçar arquivo em seis lugares, sua estrutura está por tipo — e cada mudança vai custar mais do que devia.

Nenhuma dessas práticas é revolução; são decisões que você toma uma vez e colhe por anos. Comece pelo que dói mais no seu projeto hoje — OnPush se tem lag, async pipe se tem leak, feature-folders se você se perde pra achar as coisas — e vá adotando o resto por padrão. Código Angular sustentável não vem de framework novo; vem de parar de adiar essas escolhas.

Gostou do artigo?

Compartilhe com seus amigos e ajude a espalhar conhecimento!