În Angular modern, performanța bună vine mai ales din valorile implicite: OnPush, semnale, zoneless, rute lazy, @defer. Lista de mai jos e ce verifici când ceva e lent — în ordinea în care merită. Dacă aplicația devine mai lentă cu cât stă tabul deschis mai mult, de obicei nu e randarea, ci un memory leak.
Checklist
RANDARE
[ ] Componente OnPush (implicit în v22) + stare în semnale
[ ] @for cu track pe un id stabil (nu identitate pe date reîncărcate)
[ ] Calcule în computed() / pipe pur, nu în metode apelate din template
[ ] Liste foarte mari → virtual scroll (CDK ScrollingModule) sau paginare
ÎNCĂRCARE
[ ] Toate paginile lazy (loadComponent / loadChildren)
[ ] Componente grele sub fold → @defer (on viewport / interaction)
[ ] Servicii grele, rar folosite → injectAsync() (v22)
[ ] Imagini cu NgOptimizedImage (ngSrc, width/height, priority pe LCP)
[ ] Bugete de bundle în angular.json; verifică ce intră cu source-map-explorer
SSR (dacă e cazul)
[ ] Hidratare incrementală (implicită în v22) + @defer (hydrate on ...)
[ ] Event replay pentru click-urile din timpul hidratării
[ ] TransferCache pentru request-urile făcute pe servertrack contează mai mult decât crezi
Când date reîncărcate de pe server ajung ca obiecte noi, track row (identitate) face ca Angular să considere fiecare rând „nou” și să distrugă/recreeze tot DOM-ul. Cu track row.id, refolosește rândurile existente și actualizează doar ce s-a schimbat.
Bonus: în dev mode, Angular detectează singur problema și afișează în consolă avertismentul NG0956 („track by identity caused re-creation of the entire collection”).
Imagini
<!-- NgOptimizedImage: lazy implicit, srcset automat, avertismente pentru dimensiuni lipsă -->
<img ngSrc="/assets/hero.jpg" width="1200" height="600" priority /> <!-- imaginea LCP -->
<img ngSrc="/assets/avatar.jpg" width="48" height="48" /> <!-- lazy -->
<img ngSrc="product.jpg" fill /> <!-- umple containerul -->Liste foarte mari
import { ScrollingModule } from '@angular/cdk/scrolling';
@Component({
imports: [ScrollingModule],
template: `
<cdk-virtual-scroll-viewport itemSize="48" class="viewport">
<div *cdkVirtualFor="let row of rows(); trackBy: trackById" class="row">{{ row.name }}</div>
</cdk-virtual-scroll-viewport>
`,
styles: `.viewport { height: 480px; }`,
})
export class BigList {
readonly rows = input.required<Row[]>();
protected trackById = (_: number, r: Row) => r.id;
}Bundle
// angular.json → configurations.production.budgets
"budgets": [
{ "type": "initial", "maximumWarning": "500kB", "maximumError": "1MB" },
{ "type": "anyComponentStyle", "maximumWarning": "4kB", "maximumError": "8kB" }
]SSR și hidratare
// app.config.server / app.config.ts cu SSR
provideClientHydration(
withEventReplay(), // click-urile din timpul hidratării nu se pierd
// withIncrementalHydration() — implicit din v22
);Capcane frecvente
- Optimizare fără măsurare: deschide Profiler-ul din Angular DevTools înainte să schimbi cod.
- Metode scumpe în template (
sortedItems()ca metodă) → rulează la fiecare randare. Fă-lecomputed. - Librării mari importate „pentru o funcție” (lodash întreg, moment.js) → importuri punctuale sau
injectAsync. @deferpe conținutul principal → LCP mai slab. Defer doar sub fold.