Skip to main content

NgOptimizedImage

Overview

NgOptimizedImage (Angular 15+) is a directive that automatically optimizes image loading for better performance and Core Web Vitals.

Key Benefits

  • Automatic srcset generation for responsive images
  • Lazy loading by default with priority loading for above-fold images
  • LCP (Largest Contentful Paint) improvement through preconnect hints
  • Built-in warnings for common performance mistakes
  • Automatic width/height to prevent layout shift

Basic Usage

Import the Directive

import { Component } from "@angular/core";
import { NgOptimizedImage } from "@angular/common";

@Component({
selector: "app-gallery",
standalone: true,
imports: [NgOptimizedImage],
templateUrl: "./gallery.component.html",
})
export class GalleryComponent {}

Use ngSrc Instead of src

<!-- Old way -->
<img src="assets/hero-image.jpg" alt="Hero" />

<!-- New way with NgOptimizedImage -->
<img ngSrc="assets/hero-image.jpg" alt="Hero" width="1200" height="600" />

Priority Images

Mark Above-the-Fold Images

<!-- For LCP images (hero, banner, etc.) -->
<img ngSrc="assets/hero.jpg" alt="Hero" width="1200" height="600" priority />

This generates:

<link rel="preload" as="image" href="assets/hero.jpg" />

Responsive Images

Automatic Srcset Generation

// image.component.ts
import { Component } from "@angular/core";
import { NgOptimizedImage, provideImageLoader } from "@angular/common";

@Component({
selector: "app-image",
standalone: true,
imports: [NgOptimizedImage],
template: `
<img ngSrc="product-image.jpg" alt="Product" width="800" height="600" sizes="(max-width: 768px) 100vw, 50vw" />
`,
})
export class ImageComponent {}

Generated output:

<img
src="product-image.jpg"
srcset="product-image.jpg?w=400 400w, product-image.jpg?w=800 800w, product-image.jpg?w=1200 1200w"
sizes="(max-width: 768px) 100vw, 50vw"
width="800"
height="600"
/>

Image Loaders

Built-in CDN Support

// main.ts
import { bootstrapApplication } from "@angular/platform-browser";
import { provideCloudflareLoader } from "@angular/common";
import { AppComponent } from "./app/app.component";

bootstrapApplication(AppComponent, {
providers: [provideCloudflareLoader("https://my-domain.com")],
});

Available Loaders

// Cloudflare
provideCloudflareLoader("https://example.com");

// Cloudinary
provideCloudinaryLoader("https://res.cloudinary.com/my-cloud");

// ImageKit
provideImageKitLoader("https://ik.imagekit.io/my-id");

// Imgix
provideImgixLoader("https://my-domain.imgix.net");

// Netlify
provideNetlifyLoader();

Custom Loader

// image-loader.ts
import { ImageLoaderConfig } from "@angular/common";

export function customImageLoader(config: ImageLoaderConfig): string {
const { src, width } = config;
return `https://my-cdn.com/${src}?w=${width}&q=75`;
}

// main.ts
import { bootstrapApplication } from "@angular/platform-browser";
import { IMAGE_LOADER } from "@angular/common";
import { customImageLoader } from "./image-loader";

bootstrapApplication(AppComponent, {
providers: [
{
provide: IMAGE_LOADER,
useValue: customImageLoader,
},
],
});

Fill Mode

Container-Based Sizing

<!-- For background-style images -->
<div class="image-container">
<img ngSrc="background.jpg" alt="Background" fill />
</div>
.image-container {
position: relative;
width: 100%;
height: 400px;
}

.image-container img {
object-fit: cover;
}

Performance Best Practices

1. Always Specify Dimensions

<!-- ❌ Bad: Layout shift -->
<img ngSrc="image.jpg" alt="Image" />

<!-- ✅ Good: Prevents layout shift -->
<img ngSrc="image.jpg" alt="Image" width="800" height="600" />

2. Use Priority for LCP Images

<!-- Hero image - first thing users see -->
<img ngSrc="hero.jpg" alt="Hero" width="1200" height="600" priority />

3. Lazy Load Below-the-Fold Images

<!-- Default behavior - lazy loads automatically -->
<img ngSrc="gallery-item.jpg" alt="Gallery" width="400" height="300" />

4. Provide Sizes Attribute

<img
ngSrc="responsive.jpg"
alt="Responsive"
width="1200"
height="800"
sizes="(max-width: 640px) 100vw,
(max-width: 1024px) 50vw,
33vw"
/>

Built-in Warnings

NgOptimizedImage provides helpful warnings:

Oversized Images

Warning: Image is significantly larger than displayed size.
Consider using a smaller image or responsive srcset.

Missing Dimensions

Warning: Width and height attributes are required.
This prevents layout shift.

LCP Image Not Using Priority

Warning: This image is likely an LCP element but is not marked as priority.
Consider adding the 'priority' attribute.

Real-World Example

// product-gallery.component.ts
import { Component } from "@angular/core";
import { CommonModule, NgOptimizedImage } from "@angular/common";

@Component({
selector: "app-product-gallery",
standalone: true,
imports: [CommonModule, NgOptimizedImage],
template: `
<div class="gallery">
<!-- Hero image with priority -->
<div class="hero">
<img ngSrc="products/hero.jpg" alt="Featured Product" width="1200" height="600" priority sizes="100vw" />
</div>

<!-- Lazy-loaded grid items -->
<div class="grid">
@for (product of products; track product.id) {
<div class="product-card">
<img
[ngSrc]="product.image"
[alt]="product.name"
width="400"
height="400"
sizes="(max-width: 768px) 100vw,
(max-width: 1024px) 50vw,
25vw"
/>
</div>
}
</div>
</div>
`,
styles: [
`
.hero {
position: relative;
width: 100%;
aspect-ratio: 2/1;
}

.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1rem;
}

.product-card {
position: relative;
aspect-ratio: 1;
}

img {
width: 100%;
height: 100%;
object-fit: cover;
}
`,
],
})
export class ProductGalleryComponent {
products = [
{ id: 1, name: "Product 1", image: "products/1.jpg" },
{ id: 2, name: "Product 2", image: "products/2.jpg" },
{ id: 3, name: "Product 3", image: "products/3.jpg" },
];
}

Measuring Performance

Before NgOptimizedImage

// Typical metrics
LCP: 3.2s
CLS: 0.15
Image size: 2.4MB

After NgOptimizedImage

// Improved metrics
LCP: 1.8s (-44%)
CLS: 0.02 (-87%)
Image size: 180KB (-92%)

Interview Questions

Q: What is NgOptimizedImage and why use it? A: It's an Angular directive that automatically optimizes images with srcset generation, lazy loading, and LCP improvements, leading to better Core Web Vitals.

Q: What does the priority attribute do? A: It marks above-the-fold images for immediate loading and generates preload hints, improving LCP for critical images.

Q: How does NgOptimizedImage prevent layout shift? A: It requires width/height attributes, allowing the browser to reserve space before the image loads, preventing CLS.

Q: Can you use NgOptimizedImage with CDNs? A: Yes, it has built-in loaders for Cloudflare, Cloudinary, Imgix, and more, or you can create custom loaders.