NgOptimizedImage
Overview
NgOptimizedImage (Angular 15+) is a directive that automatically optimizes image loading for better performance and Core Web Vitals.
Key Benefits
- Automatic
srcsetgeneration 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.