← Retour au blog
AngularPerformanceImagesLazy LoadingWeb Vitals

Optimizing Images: ng-srcset and Native Lazy Loading Without Slowing Down Angular

Images are the primary culprit behind slow pages. They easily account for 50 to 70 percent of total web application weight, and every unoptimized byte translates to degraded Core Web Vitals, especially LCP (Largest Contentful Paint) and CLS (Cumulative Layout Shift). In an Angular application, this burden becomes even more critical: the initial bundle must be small, rendering must be fast, and every image must be served at the right format, resolution, and time. This is precisely where ng-srcset and the browser's native lazy loading come in. Together, they form a formidable strategy for delivering optimal images without JavaScript overhead.

The Problem: Why Images Slow Down Angular

An unoptimized image creates multiple cascading problems. First, it delays initial download, directly impacting LCP, one of the three Core Web Vitals. Second, if the resolution isn't adapted to the user's screen, they download an image far too large for their device: a mobile user receives the desktop version at 2 MB when a mobile variant at 400 KB would suffice. Third, without lazy loading, all images are loaded, even those far below the fold that the user will never see. In Angular, adding an image via a simple <img [src]="myImage"> receives no native optimization: no modern formats (WebP), no screen adaptation, no deferred loading. This is why the ng-srcset directive and the native loading="lazy" attribute are essential.

ng-srcset: Adapting Images to Every Screen

The ng-srcset directive from Angular (available since Angular 14) encapsulates the complex logic of the HTML srcset attribute and makes it declarative and reactive. Instead of manually managing strings like image-small.webp 480w, image-large.webp 1024w , you declare image variants in your template and let Angular negotiate with the browser. The browser then selects the most appropriate image based on the device's pixel density (devicePixelRatio) and available width. Concretely, if a user has a 4G connection and a Retina display (2x), the browser can select a high-resolution version without you writing a single line of JavaScript to detect the device.

<img
  ngSrcset="
    images/hero-480.webp 480w,
    images/hero-1024.webp 1024w,
    images/hero-2048.webp 2048w
  "
  sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 33vw"
  ngSrc="images/hero-2048.webp"
  alt="Hero banner"
  width="1920"
  height="1080"
/>

In this example, the sizes attribute tells the browser that on mobile (≤600px), the image occupies 100% of the viewport width, on tablet 50%, and on desktop 33%. The browser uses this information to calculate the actual image width and select the appropriate variant from ngSrcset . The ngSrc directive acts as a fallback and should always point to the high-resolution version. The width and height attributes are mandatory to prevent CLS: they inform the browser of the aspect ratio, allowing the rendering engine to reserve space before the image even loads.

Native Lazy Loading: Loading Only What's Visible

The browser's native lazy loading, activated by the loading="lazy" attribute, differs fundamentally from JavaScript lazy loading (Intersection Observer). Instead of loading an image when it enters the viewport, the browser loads it slightly before, according to its internal strategy. This approach drastically reduces application-side JavaScript and delegates optimization to the browser engine, which has far more information (estimated network connection, CPU load, etc.). For images far below the fold, the gain is spectacular: a page with 50 images below the fold can save 10+ MB in initial downloads.

<img
  ngSrcset="
    images/card-300.webp 300w,
    images/card-600.webp 600w
  "
  sizes="(max-width: 768px) 100vw, 50vw"
  ngSrc="images/card-600.webp"
  alt="Product card"
  width="600"
  height="400"
  loading="lazy"
/>

In production, combining ngSrcset and loading="lazy" creates powerful synergy. The browser waits for the image to approach the viewport, then loads only the variant adapted to the device. No JavaScript is needed to detect resolution, calculate width, or manage events. The cognitive and computational load vanishes.

Modern Formats and Critical Image Prioritization

Serving WebP or AVIF (25–35% more compact than JPEG/PNG) is crucial, but only if the browser supports them. The most robust approach is to use the <picture> element with multiple sources, but this quickly explodes in verbosity. An elegant alternative: create an Angular pipe that automatically generates WebP variants and JPEG fallbacks from a base URL. Some CDN services (Cloudinary, Imgix) offer an API for this: a single URL generates all variants and formats based on query parameters.

<picture>
  <source
    ngSrcset="
      images/hero.webp?w=480 480w,
      images/hero.webp?w=1024 1024w
    "
    type="image/webp"
    sizes="(max-width: 600px) 100vw, 50vw"
  />
  <img
    ngSrcset="
      images/hero.jpg?w=480 480w,
      images/hero.jpg?w=1024 1024w"
    sizes="(max-width: 600px) 100vw, 50vw"
    ngSrc="images/hero.jpg?w=2048"
    alt="Hero"
    width="1920"
    height="1080"
    loading="lazy"
  />
</picture>

For above-the-fold images (Hero, Header), never use loading="lazy" . Instead, use fetchpriority="high" to signal to the browser that this image is critical. Conversely, for below-the-fold images or icons, loading="lazy" is the norm.

The Common Pitfall: Forgetting Dimensions and Creating CLS

Many developers add ngSrcset and loading="lazy" but omit the width and height attributes. Result: the browser has no idea how much space to reserve for the image. When the image arrives, the layout shifts dramatically, generating high CLS and poor user experience. CLS impacts Google rankings and the perceived experience of the application. Additionally, without dimensions, the browser cannot correctly calculate the ratio to choose the variant in srcset . This is an insidious pitfall because the application works visually, but Core Web Vitals suffer.

Another pitfall: mixing ngSrc with native srcset without ngSrcset . Angular optimizes ngSrc (adds cache-busting timestamp, validation), but if you write srcset directly, these optimizations are bypassed. Always prefer ngSrcset when possible.

Production Integration: CDN and Cache Headers

In production, images must be served via a CDN with aggressive cache headers (Cache-Control: public, max-age=31536000). Image URLs must include a content hash or version, ensuring that every modification generates a new URL and invalidates the cache immediately. If you're using ng-srcset with static URLs, ensure your build pipeline injects this hash. Cloudinary, Imgix, or even Nginx + ImageMagick can handle this automatically.

For Angular, consider the NgOptimizedImage directive (evolution of ngSrcset in Angular 15+), which offers strict attribute validation and development warnings if best practices aren't followed. This directive enforces the inclusion of width , height , and alt , reducing errors.

Operational Conclusion

Optimizing images in Angular boils down to three concrete actions. First, replace all <img src> with <img ngSrc> or <img ngSrcset> with explicit dimensions. Second, declare sizes to adapt the image to each breakpoint, and use modern formats (WebP) with fallbacks. Third, add loading="lazy" everywhere except for critical images (above the fold), and use fetchpriority="high" for those. The result: LCP reduced by 30–50%, CLS near zero, and spectacular bandwidth savings. It's a few hours of investment for lasting gains in performance and SEO.

Développeur Angular & Mobile freelance — Strasbourg.

© 2026 Emilien Pons — Tous droits réservés.Conçu avec Angular, PrimeNG et ❤️