Im modernen Angular kommt gute Performance vor allem von den Standardeinstellungen: OnPush, Signals, zoneless, Lazy Routes, @defer. Die Checkliste unten gehst du durch, wenn etwas langsam ist — in der Reihenfolge, in der es sich lohnt. Wird die App langsamer, je länger der Tab offen ist, liegt es meist nicht am Rendern, sondern an einem Memory Leak.
Checkliste
RENDERING
[ ] OnPush-Komponenten (Standard in v22) + Zustand in Signals
[ ] @for mit track auf eine stabile ID (nicht Identität bei neu geladenen Daten)
[ ] Berechnungen in computed() / einer reinen Pipe, nicht in Methoden, die das Template aufruft
[ ] Sehr große Listen → Virtual Scroll (CDK ScrollingModule) oder Paginierung
LADEN
[ ] Alle Seiten lazy (loadComponent / loadChildren)
[ ] Schwere Komponenten unterhalb des sichtbaren Bereichs → @defer (on viewport / interaction)
[ ] Schwere, selten genutzte Services → injectAsync() (v22)
[ ] Bilder mit NgOptimizedImage (ngSrc, width/height, priority beim LCP-Bild)
[ ] Bundle-Budgets in angular.json; mit source-map-explorer prüfen, was drin landet
SSR (falls zutreffend)
[ ] Inkrementelle Hydration (Standard in v22) + @defer (hydrate on ...)
[ ] Event Replay für Klicks während der Hydration
[ ] TransferCache für die Requests, die auf dem Server laufentrack ist wichtiger, als du denkst
Kommen vom Server neu geladene Daten als neue Objekte an, hält Angular bei track row (Identität) jede Zeile für „neu“ und zerstört bzw. erzeugt das gesamte DOM neu. Mit track row.id werden die vorhandenen Zeilen wiederverwendet und nur das aktualisiert, was sich geändert hat.
Bonus: Im Dev-Modus erkennt Angular das Problem selbst und gibt die Warnung NG0956 („track by identity caused re-creation of the entire collection“) aus.
Bilder
<!-- NgOptimizedImage: standardmäßig lazy, automatisches srcset, Warnungen bei fehlenden Maßen -->
<img ngSrc="/assets/hero.jpg" width="1200" height="600" priority /> <!-- das LCP-Bild -->
<img ngSrc="/assets/avatar.jpg" width="48" height="48" /> <!-- lazy -->
<img ngSrc="product.jpg" fill /> <!-- füllt den Container -->Sehr große Listen
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 und Hydration
// app.config.server / app.config.ts mit SSR
provideClientHydration(
withEventReplay(), // Klicks während der Hydration gehen nicht verloren
// withIncrementalHydration() — Standard seit v22
);Häufige Stolperfallen
- Optimieren ohne zu messen: Öffne den Profiler in den Angular DevTools, bevor du Code änderst.
- Teure Methoden im Template (
sortedItems()als Methode) → sie laufen bei jedem Rendering. Mach darauscomputed. - Große Bibliotheken, die „für eine Funktion“ importiert werden (ganz lodash, moment.js) → gezielte Importe oder
injectAsync. @deferauf dem Hauptinhalt → schlechterer LCP. Verzögere nur, was unterhalb des sichtbaren Bereichs liegt.