DEV Community

Cover image for 5 Técnicas Para Melhorar a Performance no seu App Angular
Adriano P. Araujo
Adriano P. Araujo

Posted on

5 Técnicas Para Melhorar a Performance no seu App Angular

O Problema

muk

Lembra daquele app que começou rápido e depois foi ficando mais lento conforme adicionávamos features? Pois é, o culpado quase sempre é o mesmo: change detection.

O Angular, por padrão, é um pouco paranóico. A cada evento (scroll, clique, timer), ele verifica TODA a árvore de componentes pra ver se algo mudou.

Traduzindo: se você tem 100 componentes e 100 eventos por segundo, são 10.000 verificações por segundo. Não tem CPU que aguente!

Para resolver, listei abaixo 5 técnicas para melhorar a performance do seu app Angular. Algumas delas, mesmo sendo velhas conhecidas, ainda hoje costumam ser ignoradas.


1. OnPush - A Estratégia Mais Importante

Compatibilidade: Angular 2+
Impacto: Reduz change detection em ~90%

Essa aqui muda o jogo. O OnPush faz o Angular ser mais inteligente - ele só verifica mudanças quando realmente precisa.

@Component({
  selector: 'app-user-card',
  template: `<div>{{ user.name }}</div>`,
  changeDetection: ChangeDetectionStrategy.OnPush  // 👈 Essa mágica aqui
})
export class UserCardComponent {
  @Input() user: User;
}
Enter fullscreen mode Exit fullscreen mode

A pegadinha: Com OnPush, você precisa parar de mutar objetos:

// ❌ Isso quebra tudo (o Angular não detecta)
this.user.name = 'João';

// ✅ Isso funciona perfeitamente
this.user = { ...this.user, name: 'João' };
Enter fullscreen mode Exit fullscreen mode

Recomendação: Todo componente novo já deveria nascer com OnPush. Só remove quando realmente não tem jeito (e isso é raro).


2. Signals - O Futuro Que Já Chegou

Compatibilidade: Angular 17+
Impacto: Mais eficiente que RxJS

Signals são mais simples que RxJS e mais eficientes. Depois de testá-las, viram a primeira escolha para muitos devs.

@Component({
  selector: 'app-counter',
  template: `
    <div>Count: {{ count() }}</div>    <!-- Note os () -->
    <div>Doubled: {{ doubled() }}</div>
  `
})
export class CounterComponent {
  count = signal(0);           // 👈 Declaração simples
  doubled = computed(() => this.count() * 2);  // 👈 Computed elegante
}
Enter fullscreen mode Exit fullscreen mode

Por que Signals sobre RxJS:

// ❌ RxJS (funciona, mas é mais verboso)
count$ = new BehaviorSubject(0);
doubled$ = this.count$.pipe(map(c => c * 2));

// ✅ Signals (mais simples e eficiente)
count = signal(0);
doubled = computed(() => this.count() * 2);
Enter fullscreen mode Exit fullscreen mode

A grande vantagem: com Signals, só os componentes que usam aquele signal específico são verificados. É muito mais granular.

Nota pra quem está em Angular 12-16: RxJS + OnPush funciona muito bem também!


3. Lazy Loading - Dividir para Conquistar

Compatibilidade: Angular 14+
Impacto: Isola componentes pesados

Componentes pesados ou telas menos acessadas devem ser lazy-loaded:

const routes: Routes = [
  {
    path: 'dashboard',
    loadComponent: () => import('./dashboard/dashboard.component')
      .then(m => m.DashboardComponent)  // 👈 Só carrega quando acessado
  }
];
Enter fullscreen mode Exit fullscreen mode

Resultado: O app principal fica leve e rápido. O dashboard só carrega quando o usuário realmente precisa.


4. Unsubscribe - Evitando Vazamentos

Padrão recomendado (Clássico): takeUntil com Subject
Padrão moderno (Angular 16+): takeUntilDestroyed
Problema que resolve: Memory leaks e change detection desnecessário

Abordagem Clássica (Angular 2+)

export class DataComponent implements OnInit, OnDestroy {
  private destroy$ = new Subject<void>();  // 👈 Subject de limpeza

  ngOnInit() {
    this.dataService.getData()
      .pipe(
        takeUntil(this.destroy$)  // 👈 Limpa automaticamente
      )
      .subscribe(data => {
        this.data = data;
      });
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }
}
Enter fullscreen mode Exit fullscreen mode

Abordagem Moderna (Angular 16+)

import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

@Component({
  selector: 'app-data',
  template: `<div>{{ data }}</div>`
})
export class DataComponent implements OnInit {
  private destroyRef = inject(DestroyRef);
  data: any;

  constructor(private dataService: DataService) {}

  ngOnInit() {
    this.dataService.getData()
      .pipe(
        takeUntilDestroyed(this.destroyRef)  // 👈 Muito mais simples!
      )
      .subscribe(data => {
        this.data = data;
      });
  }
}
Enter fullscreen mode Exit fullscreen mode

Por que funciona: Observables que continuam emitindo depois que o componente foi destruído são uma fonte comum de problemas. Esse padrão elimina o problema.

Qual usar? Se você está em Angular 16+, use takeUntilDestroyed. É mais limpo e não precisa implementar OnDestroy.


5. trackBy - O Segredo das Listas Rápidas

Compatibilidade: Angular 2+
Quando usar: Sempre em *ngFor com listas dinâmicas
Melhoria: Listas com 1000+ itens ficam fluidas

Abordagem Clássica (Angular 2+)

@Component({
  template: `
    <div *ngFor="let user of users; trackBy: trackByFn">
      {{ user.name }}
    </div>`
})
export class UserListComponent {
  users: User[] = [];

  // 👈 Essa função simples faz mágica
  trackByFn(index: number, item: User) {
    return item.id;  // O Angular usa isso pra saber o que mudou
  }
}
Enter fullscreen mode Exit fullscreen mode

Abordagem Moderna com Control Flow (Angular 17+)

@Component({
  template: `
    @for (user of users; track user.id) {
      <div>{{ user.name }}</div>
    }
  `
})
export class UserListComponent {
  users: User[] = [];
  // 👈 Não precisa de trackByFn! O 'track' é obrigatório
}
Enter fullscreen mode Exit fullscreen mode

Antes: A lista inteira era re-renderizada a cada mudança
Depois: Só os itens que realmente mudaram são atualizados

Nota importante: No Angular 17+, o track é obrigatório na nova sintaxe de control flow (@for). Isso força boas práticas de performance desde o início.


Checklist de Performance

checking

Antes de entregar qualquer feature, é legal verificar:

  • Todos componentes usando OnPush?
  • Signals implementados (se Angular 17+)?
  • Componentes pesados em lazy loading?
  • Observables com takeUntil ou takeUntilDestroyed?
  • O trackBy em todas as listas (ou @for com track)?
  • Testei no Performance Profiler?

Conclusão Prática

Comece com OnPush em tudo. Depois adote Signals onde possível (Angular 17+). Finalmente, mantenha um processo consistente de lazy loading + unsubscribe + trackBy.

Se você está em Angular 17+, aproveite as novas features: takeUntilDestroyed e control flow com @for. Elas tornam o código mais limpo e forçam boas práticas de performance.

Os resultados? Apps que mantêm a performance não importa quantas features adicionemos.

Just Code It!

Top comments (2)

Collapse
 
geromegrignon profile image
Gérôme Grignon

4 and 5 are already outdated as we now use takeUntilDestroyed and 'track' with control flow

Collapse
 
dev-araujo profile image
Adriano P. Araujo

I had already made a note about track, but I'll clarify it further and add a point about takeUntilDestroyed for those using Angular 17+ (which hasn't yet been widely adopted by Brazilian companies).
Thank you for your contribution. ^