Advanced
Performance
Master Angular Performance Optimization, covering NgOptimizedImage, Deferrable Views (@defer), code splitting, and list tracking.
1. Learning Objectives
In this lesson, you will master Angular performance optimization techniques. By the end of this topic, you will be able to:
- Optimize image delivery and prevent Cumulative Layout Shift (CLS) using
NgOptimizedImage. - Implement deferrable views using the modern
@deferblock template syntax. - Select and configure appropriate loading triggers (idle, viewport, hover, interaction) for lazy code chunk loading.
- Structure route-level lazy loading to optimize initial application bundle size.
- Optimize list rendering in templates using the
trackexpression inside loops.
2. Overview
Angular Performance Optimization involves techniques that reduce the size of initial load bundles and speed up component rendering loops. By splitting large modules into deferred chunks that only load on-demand, caching rendering outputs, and optimizing asset delivery (like images), you can achieve high core web vitals. Key APIs include NgOptimizedImage, the @defer template engine, and route-level lazy loading.
3. Why This Topic Matters
Poor optimization results in high bounce rates and poor search engine rankings:
- Initial Bundle Bloat: If you import a heavy charting library or comments widget on your home page, users must download megabytes of JavaScript before they see anything, severely impacting Largest Contentful Paint (LCP).
- Layout Shifts: Images loading without explicit dimensions cause content to jump around the page during loads. This poor user experience, called Cumulative Layout Shift (CLS), is heavily penalized by search engines.
4. Real-World Analogy
Think of optimizing your Angular application like **planning a moving truck**:
- Unoptimized loading: Packing every item you own (winter clothes, lawn mowers, old books) into the first truck. You spend hours moving items you won't need for months, making the move slow and tiring.
- Deferred views (@defer): Packing only what you need for the first day (bed sheets, toothbrush, work laptop). You leave the lawn mowers and winter clothes in storage (split chunks) and only call the delivery service (triggers) to bring them when it's spring (on viewport) or when you need them (on interaction).
5. Core Concepts
Below are the primary mechanisms for loading template content lazily:
| Trigger Option | Description | Ideal Use Case |
|---|---|---|
| on idle | Loads chunk in the background after browser becomes idle (Default trigger). | Below-the-fold content that is likely to be viewed soon. |
| on viewport | Loads chunk when placeholder enters browser viewport (Intersection Observer). | Heavy charts, maps, or image sliders further down the page. |
| on interaction | Loads chunk when user clicks or focuses on the placeholder element. | Expansions, dropdowns, modal windows, or dynamic tabs. |
| on hover | Loads chunk when user hovers mouse over the placeholder. | Detailed tooltips, previews, or help panels. |
6. Syntax & API Reference
This template shows how to implement deferrable views with placeholders, loading templates, and error boundaries:
7. Visual Diagram
This diagram displays the state transitions of a Defer block lifecycle:
8. Live Example — Full Working Code
Below is a complete optimized dashboard component that lazy loads an interactive comment panel when hovered over, and manages image optimizations:
What just happened? Under normal circumstances, CommentSectionComponent's code compiles directly into the main dashboard bundle. By placing it inside @defer (on hover), Angular splits it into a separate JavaScript chunk. The user initially sees the dashed placeholder box. The moment the mouse hovers over this placeholder, the browser fetches the comment section bundle and renders it seamlessly.
9. Interactive Playground
Try It Yourself Challenges:
- Change the dashboard trigger from
on hovertoon interaction. Verify in your browser DevTools network tab that clicking the placeholder launches the module download. - Test combining triggers: write
@defer (on viewport; when dataLoaded)wheredataLoadedis a boolean component variable. Verify the module only loads when both criteria are met.
10. Common Mistakes
| Mistake | Why it happens | Wrong | Correct |
|---|---|---|---|
| Omitting Width/Height on NgOptimizedImage | Failing to set dimensions (unless using fill layout) throws compiler warnings and prevents layout shift calculations. | <img ngSrc="img.png"> |
<img ngSrc="img.png" width="300" height="200"> |
| Forgetting Track in loops | Using loops without tracking ID causes Angular to delete and re-recreate every single DOM node on array changes. | @for (item of items; track $index) (if list order changes) |
@for (item of items; track item.id) |
11. Best Practices
- Use track inside loops: Always provide a unique tracking key (e.g.
track item.id) in loops rather than falling back to array index tracking. - Set image priority: Enable the
priorityattribute on images located in the initial viewport (above-the-fold) to trigger preload optimizations. - Add placeholder boundaries: Provide a height to placeholder blocks inside
@defertemplates to prevent page layout jumps when components load. - Lazy load route components: Leverage loadComponent in routing configurations to split pages into distinct JS bundles.
12. Browser Compatibility/Requirements
Deferrable views and image directives run in all modern web browsers. Viewport tracking leverages the native browser `IntersectionObserver` API.
13. Interview Questions
Q1: What are the main benefits of the NgOptimizedImage directive?
Answer:
- Prevents layout shifts (CLS) by enforcing dimension ratios.
- Generates image source-sets (srcset) automatically for differing screens.
- Auto-enables lazy loading for below-the-fold images.
- Warns developer if images are scaled poorly relative to parent dimensions.
Q2: How does the @defer block differ from using *ngIf for hiding components?
Answer: *ngIf only hides or shows components logically in the template, but the component's JavaScript code must still be downloaded, parsed, and executed in the main initial bundle. The @defer block completely splits the component into a separate bundle chunk that is only downloaded from the server when specified triggers fire.
14. Debugging Exercise
Identify why deferrable view split chunk is loaded immediately on page load, rendering the placeholder useless:
Diagnosis: Defer blocks trigger chunk splitting for components declared *inside* the block. However, if the component is imported or referenced elsewhere outside a defer block, or if the template placeholder is positioned inside the active initial view (meaning the viewport trigger condition matches immediately on startup), the chunk loads eagerly.
Fix: Ensure the placeholder element is set below the initial viewport scroll boundary, and confirm the deferred child component isn't explicitly imported or rendered elsewhere eagerly in the layout.
15. Practice Exercises
Exercise 1: Defer map rendering below viewport
Build a layout containing a heavy Google Map stub component. Wrap the map in a @defer block using the on viewport trigger, ensuring the map is placed far enough down to require scrolling to initiate code loading.
16. Scenario-Based Challenge
The Dynamic News Feed Challenge:
You are designing a high-traffic news platform. The homepage lists 100 articles, each displaying author images and widgets. Rendering all nodes concurrently slows scrolling down. Design an optimization plan combining @defer blocks for comment frames, NgOptimizedImage priorities for banners, and custom loops with unique id tracking keys to ensure infinite scrolling runs at 60 FPS.
17. Quick Quiz
Q1: Which directive attribute should you apply to prioritize preloading for main banner images?
A) loading="lazy"
B) priority
C) preload="true"
Answer: B — Applying the 'priority' attribute triggers image preloading flags in the browser.
18. Summary & Key Takeaways
- Image optimizations (NgOptimizedImage) prevent layout shifts (CLS).
- Deferrable views (@defer) split template components into separate JS chunks.
- Triggers like viewport and interaction control when code is loaded.
- Loop optimizations require unique tracking expressions to prevent DOM rebuilds.
19. Cheat Sheet
| Directive / Blocks | Purpose |
|---|---|
<img ngSrc="url"> |
Enforces optimized image loading patterns. |
@defer (on viewport) {} |
Splits contents and loads chunk when scrolled into view. |
@placeholder (minimum Xms) |
Displays fallback content prior to trigger execution. |