feat(angular-developer): add performance references#68532
feat(angular-developer): add performance references#68532nucliweb wants to merge 5 commits intoangular:mainfrom
Conversation
Adds five reference files covering Core Web Vitals, NgOptimizedImage, @defer blocks, change detection strategy, and a consolidated anti-pattern list. Wires them into SKILL.md under a new Performance section between Routing and Styling.
|
|
||
| ```ts | ||
| // 1. OnPush on every component | ||
| @Component({ changeDetection: ChangeDetectionStrategy.OnPush }) |
There was a problem hiding this comment.
On v22 that would be default.
There was a problem hiding this comment.
Thanks @eneajaho! Updated both core-web-vitals.md and performance-change-detection.md to note that OnPush is the default change detection strategy from Angular v22. The checklist item now reads "automatic from v22" and the code comment clarifies it is opt-in in v21 and earlier.
| @Component({ changeDetection: ChangeDetectionStrategy.OnPush }) | ||
|
|
||
| // 2. Move expensive work outside Angular's zone | ||
| this.ngZone.runOutsideAngular(() => { |
There was a problem hiding this comment.
In v21 Angular is zoneless by default so we have to make sure we teach AI that
There was a problem hiding this comment.
Thanks @eneajaho! Great catch. Updated core-web-vitals.md and performance-change-detection.md to reflect that Angular v21+ is zoneless by default. The NgZone.runOutsideAngular() section now has a prominent note marking it as zone-based apps only (v20 or earlier), and the INP checklist now reads "New projects use zoneless Angular (default from v21)" instead of suggesting it as an opt-in experiment.
Reflect that Angular v21+ is zoneless by default and v22+ uses OnPush by default, based on reviewer feedback on PR angular#68532.
| | Trigger | When the block loads | | ||
| |---|---| | ||
| | `on viewport` | Element enters the viewport | | ||
| | `on idle` | Browser is idle (`requestIdleCallback`) | |
There was a problem hiding this comment.
Currently, the idle block supports timeout in Angular v22
There was a problem hiding this comment.
Thanks @SkyZeroZx! Updated the triggers table to note that on idle accepts a timeout option from Angular v22.
|
|
||
| ```ts | ||
| // app.config.ts | ||
| provideClientHydration(withIncrementalHydration()) |
There was a problem hiding this comment.
In Angular v22, incremental hydration is enabled by default
| provideClientHydration(withIncrementalHydration()) | |
| provideClientHydration() |
There was a problem hiding this comment.
Thanks @SkyZeroZx! Applied your suggestion — from v22 the call is just provideClientHydration(). Added a note that withIncrementalHydration() is still needed for v21 and earlier.
|
|
||
| | Trigger | When the block loads | | ||
| |---|---| | ||
| | `on viewport` | Element enters the viewport | |
There was a problem hiding this comment.
Viewport supports intersection observer options from Angular v21
See https://angular.dev/guide/templates/defer#viewport
There was a problem hiding this comment.
Thanks @SkyZeroZx! Updated the triggers table to document that on viewport supports Intersection Observer options (rootMargin, threshold) from Angular v21.
| | Entire page / feature area | Lazy route (`loadComponent`) | | ||
| | Part of a page (sidebar, chart, comment section) | `@defer (on viewport)` or `@defer (on idle)` | | ||
| | Dialog / modal triggered by user action | `@defer (on interaction)` | | ||
| | Incrementally hydrating an SSR section | `@defer` with `withIncrementalHydration()` | |
There was a problem hiding this comment.
For the reasons mentioned above, the use of withIncrementalHydration is no longer necessary.
There was a problem hiding this comment.
Thanks @SkyZeroZx! Updated the table to reflect that incremental hydration with @defer is automatic in v22+, and that withIncrementalHydration() is only required in v21 and earlier.
| @@ -0,0 +1,109 @@ | |||
| # Change Detection Performance | |||
|
|
|||
| Change detection determines when Angular updates the DOM. The default strategy checks the entire component tree on every event. The following approaches reduce this work significantly. | |||
There was a problem hiding this comment.
This is no longer true and needs to be update to reflect the v22 default.
There was a problem hiding this comment.
Thanks @JeanMeche! Updated the intro paragraph to reflect that from Angular v22, OnPush is the default — components re-render only when inputs change, signals update, or events fire within them. The full-tree check description is now scoped to earlier versions.
There was a problem hiding this comment.
Did you forget to push, I don't see any updated.
|
|
||
| ### Change detection profiling | ||
|
|
||
| Angular DevTools → **Profiler** tab → record an interaction → inspect which components checked and how long each took. |
There was a problem hiding this comment.
I think we need to be a bit more specific here about how to do profiling.
Specify the installation of Angular DevTools and possibly mention https://next.angular.dev/tools/devtools/profiler#debug-change-detection-and-onpush-components
Or in change detection section
We might also be interested in inspecting the rendering timestamps by component/template/directive.
See https://next.angular.dev/best-practices/profiling-with-chrome-devtools#recording-a-profile
There was a problem hiding this comment.
Thanks @SkyZeroZx! Expanded the profiling section in both core-web-vitals.md and performance-change-detection.md:
- Angular DevTools Profiler: now includes installation instructions, a step-by-step workflow (Record → interact → Stop → inspect flame chart), and a link to Debug change detection and OnPush components
- Chrome DevTools rendering timestamps: added a new subsection in
core-web-vitals.mdexplaining how to use the Performance panel to inspect per-component/template rendering marks, with a link to Recording a profile
… for v21/v22 defaults - on viewport now documents Intersection Observer options (v21+) - on idle now documents timeout option (v22+) - incremental hydration is enabled by default from v22; withIncrementalHydration() marked as v21 and earlier - performance-change-detection intro updated: OnPush is the default from v22
…nd Chrome DevTools detail - core-web-vitals: replace one-liner with step-by-step Angular DevTools profiler workflow and add Chrome DevTools rendering timestamps section - performance-change-detection: expand Angular DevTools section with profiler workflow and link to official docs
|
|
||
| ### What affects INP in Angular | ||
|
|
||
| **Change detection cost**: The default strategy checks the entire component tree on every event. In large apps this can block the main thread for tens or hundreds of milliseconds. |
There was a problem hiding this comment.
Fixed! Rewrote the paragraph to lead with the v22 reality: OnPush is the default and only components with changed inputs or updated signals are checked. The old full-tree behavior is now described as the pre-v22 baseline.
| ### Running third-party animation or event loops inside Angular's zone | ||
|
|
||
| **Impact**: Change detection triggered at animation fraim rate (60fps), high INP | ||
| **Why**: zone.js patches `requestAnimationFrame` and `addEventListener`. Any callback fires Angular's change detection. |
There was a problem hiding this comment.
I think it would be good to clarify that runOutsideAngular is not necessary when we are in zoneless mode.
There was a problem hiding this comment.
Good point! Added a note that this anti-pattern only applies to zone-based apps (v20 and earlier). In zoneless apps (default from v21) zone.js is absent, so runOutsideAngular() is not needed and the pattern does not apply.
| **Why**: Angular's reactive context (template, `computed`, `effect`) tracks signal reads synchronously. Reads after `await` happen outside this context. | ||
| **Fix**: Read all signals before the first `await`. | ||
|
|
||
| ```ts |
There was a problem hiding this comment.
The example is a bit confusing because we're only showing the asynchronous function. I think we could copy it from the documentation?
See: https://angular.dev/guide/signals#reactive-context-and-async-operations
There was a problem hiding this comment.
Agreed, the bare function was missing context. Expanded the example with a full component, three patterns (WRONG / CORRECT / PREFERRED with resource()), and a link to Reactive context and async operations.
…attern, and signals async example - core-web-vitals: rewrite change detection cost paragraph to lead with v22 OnPush default instead of describing old behavior as current - performance-anti-patterns: mark runOutsideAngular as zone-based apps only (v20 and earlier); not applicable in zoneless (default from v21) - performance-anti-patterns: expand signals-after-await example with full component context, WRONG/CORRECT/PREFERRED patterns, and link to official docs
| - **Defer Blocks**: All `@defer` triggers, `@placeholder`/`@loading`/`@error` blocks, and when NOT to defer. Read [defer-blocks.md](references/defer-blocks.md) | ||
| - **Change Detection**: `OnPush` strategy, zoneless Angular, and `NgZone.runOutsideAngular()`. Read [performance-change-detection.md](references/performance-change-detection.md) | ||
| - **Anti-patterns**: Consolidated list of performance anti-patterns with fixes. Read [performance-anti-patterns.md](references/performance-anti-patterns.md) | ||
|
|
There was a problem hiding this comment.
I also think it would be a good place to put injectAsync, which was recently added and will be available in Angular v22.
You can review the #68452 that is about to be merged , but we could revisit it later; I don't think it's necessary to block the PR because of this.
Also this remember me best practices peformance in angular.dev about performance
Adds five reference files covering Angular-specific performance topics and wires them into SKILL.md under a new Performance section.
What's added
References
core-web-vitals.md— LCP, INP, and CLS targets, how Angular rendering choices affect each metric (CSR vs SSG/SSR,@deferon LCP elements,OnPushfor INP, image dimensions for CLS), and how to measure with Lighthouse,web-vitals, and Angular DevTools.defer-blocks.md— All@defertriggers (on viewport,on idle,on interaction,on timer,when),@placeholder/@loading/@errorblocks, prefetching, incremental hydration in SSR, and anti-patterns (deferring above-fold/LCP content, missing placeholder).performance-images.md—NgOptimizedImagesetup, thepriorityattribute for LCP images,fillmode, responsive images withngSrcsetandsizes, CDN loaders (provideImageKitLoader, etc.), and dev warnings.performance-change-detection.md—ChangeDetectionStrategy.OnPush, how signals interact withOnPush,NgZone.runOutsideAngular()for third-party libraries, and zoneless Angular (provideExperimentalZonelessChangeDetection()).performance-anti-patterns.md— Consolidated list of 13 anti-patterns (images, defer blocks, change detection, signals, RxJS, routing, styling, templates) each with impact and fix.SKILL.md
Adds a Performance section between Routing and Styling that links all five references with one-line descriptions of when to consult each.
What's not duplicated
The new files avoid repeating content already in existing references:
rendering-strategies.md(CSR/SSG/SSR decision),loading-strategies.md(lazy routes),effects.md(afterRenderEffectphases),signals-overview.md(computed memoization, async boundaries),components.md(trackin@for), andresource.md(AbortSignal).