Org Skills

angular-performance

Diagnose and fix Angular performance — slow change detection, ExpressionChangedAfterItHasBeenChecked, big bundles, slow initial load, janky lists/tables, memory leaks from subscriptions, and poor Core Web Vitals.

Download .zip Raw Source
When agents use itUse this whenever the user says an Angular app is slow, asks about OnPush, zoneless, signals for performance, @defer, hydration, lazy loading, trackBy/track, CDK virtual scroll, bundle budgets, or profiling with Angular DevTools. Also apply it when reviewing Angular code that uses function calls or pipes with heavy logic in templates, `*ngFor` without track, default change detection, or manual subscriptions.

Install

Copilot (VS Code, Visual Studio, Copilot CLI and github.com) reads skills from the repository — commit them so the whole team gets them.

npx skills add AGCO-Global/org-skills --skill angular-performance -a github-copilot
# or with the org installer (adds .github/skills/angular-performance):
npx -y github:AGCO-Global/org-skills add skill angular-performance

Uses the open skills CLI. Works with Claude Code, Codex, Cursor, Copilot, Gemini CLI, OpenCode, Windsurf and 60+ others — it asks which agent to install into.

npx skills add AGCO-Global/org-skills --skill angular-performance
# user-level instead of project-level:
npx skills add AGCO-Global/org-skills --skill angular-performance -g

Installs the frontend-angular-skills plugin, which bundles all Frontend / Angular skills and keeps them updated.

/plugin marketplace add AGCO-Global/org-skills
/plugin install frontend-angular-skills@org-skills
# or just this skill, in this repository:
npx skills add AGCO-Global/org-skills --skill angular-performance -a claude-code

Use Download .zip above, then upload it under Customize → Skills → Upload skill. Team and Enterprise admins can sync this repo as a plugin marketplace instead.

Installs into .agents/skills/, which Codex reads.

npx skills add AGCO-Global/org-skills --skill angular-performance -a codex

Installs into Cursor's skills folder.

npx skills add AGCO-Global/org-skills --skill angular-performance -a cursor

Installs into Gemini CLI's skills folder.

npx skills add AGCO-Global/org-skills --skill angular-performance -a gemini-cli

Any tool with rules, instructions or custom prompts: use Copy SKILL.md above and paste it in. It is plain Markdown.

Commands use your normal git sign-in to GitHub, so they work while the repository is private. Node.js 20+ required.

Skill contents

Angular Performance

Angular performance problems cluster into three buckets: change detection doing too much work, too much JavaScript shipped, and templates doing work on every check. Identify the bucket before proposing a fix.

1. Measure

Symptom Tool Metric
Interactions feel slow Angular DevTools → Profiler (record, see CD cycles and per-component time) Number of change-detection runs per interaction, longest component
Slow load Lighthouse, ng build --stats-json + esbuild analyzer / source-map-explorer LCP, initial bundle size, number of lazy chunks
Jank while scrolling/typing Chrome Performance panel Long tasks, time in tick() / refreshView
Memory growth across routes DevTools heap snapshots Retained components, subscriptions
Layout shift Lighthouse CLS Images/fonts without dimensions, late-loaded content

Capture a baseline with numbers before changing anything. Profile a production build (ng build, ng serve --configuration production), not dev.

2. Change detection — the biggest lever

Work down this list:

  1. ChangeDetectionStrategy.OnPush on every component. Default CD checks the entire tree on every event; OnPush checks a component only when an input reference changes, an event fires inside it, or a signal it reads changes. Add it, then fix what breaks — breakage means something is mutating objects in place.
  2. Signals for all component state. With signals, Angular marks only the exact components that read a changed signal. Convert @Input()input(), fields → signal(), derived → computed().
  3. Go zoneless. provideZonelessChangeDetection() removes zone.js: no more CD on every setTimeout, every fetch, every mouse move. Requires OnPush + signals everywhere; third-party libs must not rely on zone. This is the end state for new apps.
  4. Kill template function calls. {{ formatPrice(item.price) }} runs on every CD cycle. Replace with a pure pipe (memoised per input) or a computed().
  5. Pure pipes over impure. Never pure: false unless the pipe genuinely depends on external mutable state; use signals instead.
  6. @for with track on a stable id. track $index on mutable lists causes full re-renders and DOM churn; track item.id reuses nodes.
  7. Detach expensive, rarely-changing subtrees — with signals this is rarely needed; ChangeDetectorRef.detach() is a last resort.

ExpressionChangedAfterItHasBeenChecked: it means state changed during a CD pass — usually a child writing to a parent in ngAfterViewInit, or an effect() writing signals. Fix the data flow (compute upward with computed, emit through output()), don't wrap in setTimeout.

3. Initial load and bundle size

  • Lazy load every feature route (loadChildren/loadComponent); the initial bundle should contain the shell, auth, and the landing route only.
  • @defer for heavy components below the fold or behind interaction: @defer (on viewport) { <app-chart/> } @placeholder { <div class="skeleton"/> }. Triggers: on idle, on viewport, on interaction, on hover, on timer, when <condition>. Add @loading and @error blocks.
  • Bundle budgets in angular.json (initial warning ~500 kB, error ~1 MB; adjust to the app) and fail CI on breach.
  • Analyse with ng build --stats-json then esbuild's analyzer: look for moment.js (→ date-fns/Temporal), full lodash, RxJS operator misuse, whole icon fonts, Material modules imported at root instead of per component.
  • Standalone imports per component pull in only what each template uses — another reason to leave NgModules.
  • Fonts: preload one critical font, font-display: swap, subset. Images: NgOptimizedImage (ngSrc, priority on the LCP image, width/height, sizes) — it enforces best practices.
  • Preconnect to API and CDN origins in index.html.
  • Third-party scripts deferred and measured; they're frequently the entire INP problem.

4. SSR, hydration, and Core Web Vitals

  • @angular/ssr with provideClientHydration() for public pages — hydration reuses server DOM instead of re-rendering; verify no hydration mismatch warnings.
  • Incremental hydration (withIncrementalHydration() + @defer (hydrate on viewport)) so non-critical islands stay static until needed.
  • Event replay (withEventReplay()) so clicks during hydration aren't lost.
  • Avoid document/window access outside afterNextRender/isPlatformBrowser guards — it breaks SSR and forces client-only rendering.
  • Prerender static routes at build time (prerender in angular.json / getPrerenderParams).

5. Lists and tables

  • CDK Virtual Scroll (cdk-virtual-scroll-viewport + *cdkVirtualFor) for > ~100 rows; itemSize fixed when possible.
  • Server-side pagination/sorting/filtering for large datasets; never load 10k rows to filter client-side.
  • Row components OnPush with signal inputs; no functions in row templates.
  • Material Table: virtual scroll via CDK or paginate; avoid MatSort/MatPaginator on huge client arrays.
  • Debounce filter inputs (RxJS debounceTime(300) + distinctUntilChanged + switchMap), but update the input echo immediately.
  • Every manual subscribe() needs takeUntilDestroyed() (or toSignal, which cleans up). Missing teardown is the #1 Angular memory leak.
  • shareReplay({ bufferSize: 1, refCount: true }) for shared HTTP results; without refCount the source lives forever.
  • switchMap for typeahead/route param changes (cancels stale requests); exhaustMap for submit.
  • Don't create Observables in templates or getters — each CD cycle creates a new stream and AsyncPipe resubscribes.
  • Router events subscriptions in many components → centralise in one service exposing signals.

7. Runtime hygiene

  • Heavy synchronous work (sorting, parsing, crypto) → Web Worker (ng generate web-worker).
  • runOutsideAngular for high-frequency events (scroll, mousemove, resize) when still on zone.js.
  • afterRender/afterNextRender for DOM measurement instead of setTimeout(0).
  • content-visibility: auto on long off-screen sections; will-change sparingly.
  • Avoid ViewEncapsulation.None styles with deep selectors — global style recalcs get expensive.

8. Report format

## Baseline
<metric>: <value> (production build, device, network)

## Bucket
Change detection / Bundle / Template work / Memory

## Findings (impact order)
1. <problem> — <evidence from DevTools profiler / analyzer> — est. gain

## Fixes
Minimal diff each, with before/after expectation

## Verify
Same measurement repeated

Anti-patterns to reject

  • Default change detection on new components.
  • Function calls or new Date() / JSON.stringify in templates.
  • @for without track, or track $index on reorderable data.
  • setTimeout to dodge ExpressionChangedAfterItHasBeenChecked.
  • subscribe() in ngOnInit with no teardown.
  • SharedModule imported into every lazy chunk (duplicates code into each).
  • pure: false pipes for formatting.
  • Rendering all rows of a large dataset "because virtual scroll is complicated".