Lazy Loading Images
Native Lazy Loading
Section titled “Native Lazy Loading”<!-- Simple lazy loading --><img src="image.jpg" loading="lazy" alt="Description">
<!-- With dimensions (prevents layout shift) --><img src="image.jpg" loading="lazy" width="400" height="300" alt="Description">
<!-- Eager loading (for above-the-fold images) --><img src="hero.jpg" loading="eager" alt="Hero image">NgOptimizedImage
Section titled “NgOptimizedImage”import { NgOptimizedImage } from '@angular/common';
@Component({ standalone: true, imports: [NgOptimizedImage], template: ` <!-- Basic usage --> <img ngSrc="hero.jpg" width="400" height="300" alt="Hero">
<!-- Priority (above-the-fold) --> <img ngSrc="hero.jpg" width="400" height="300" priority alt="Hero">
<!-- With fill (for backgrounds) --> <div style="position: relative; height: 400px"> <img ngSrc="bg.jpg" fill alt="Background"> </div>
<!-- Responsive --> <img ngSrc="responsive.jpg" width="400" height="300" sizes="(max-width: 768px) 100vw, 50vw" alt="Responsive"> `})export class ImageComponent {}Intersection Observer Directive
Section titled “Intersection Observer Directive”import { Directive, ElementRef, output, AfterViewInit, inject } from '@angular/core';
@Directive({ selector: '[appLazyLoad]', standalone: true})export class LazyLoadDirective implements AfterViewInit { private el = inject(ElementRef); // Signal-based output lazyLoad = output<void>();
ngAfterViewInit() { const observer = new IntersectionObserver( (entries) => { entries.forEach(entry => { if (entry.isIntersecting) { this.lazyLoad.emit(); observer.unobserve(this.el.nativeElement); } }); }, { threshold: 0.1 } );
observer.observe(this.el.nativeElement); }}
// Usage@Component({ template: ` <img [src]="imageSrc" appLazyLoad (lazyLoad)="loadImage()" alt="Lazy loaded"> `})export class ImageComponent { imageSrc = 'placeholder.jpg';
loadImage() { this.imageSrc = 'actual-image.jpg'; }}Lazy Load with Placeholder
Section titled “Lazy Load with Placeholder”import { Component, input } from '@angular/core';
@Component({ selector: 'app-lazy-image', standalone: true, template: ` <img [src]="isLoaded ? actualSrc() : placeholder" (load)="onLoad()" [class.loaded]="isLoaded" alt="Image"> `, styles: [` img { opacity: 0; transition: opacity 0.3s; } img.loaded { opacity: 1; } `]})export class LazyImageComponent { actualSrc = input.required<string>(); placeholder = 'data:image/svg+xml,...'; // Tiny placeholder isLoaded = false;
onLoad() { this.isLoaded = true; }}Progressive Image Loading
Section titled “Progressive Image Loading”import { Component, input } from '@angular/core';
@Component({ selector: 'app-progressive-image', standalone: true, template: ` <div class="image-container"> <img [src]="lowResSrc()" class="low-res" [class.hidden]="highResLoaded" alt="Low resolution"> <img [src]="highResSrc()" class="high-res" (load)="highResLoaded = true" [class.visible]="highResLoaded" loading="lazy" alt="High resolution"> </div> `, styles: [` .image-container { position: relative; } img { position: absolute; width: 100%; height: 100%; } .low-res { filter: blur(10px); transition: opacity 0.3s; } .low-res.hidden { opacity: 0; } .high-res { opacity: 0; transition: opacity 0.3s; } .high-res.visible { opacity: 1; } `]})export class ProgressiveImageComponent { lowResSrc = input.required<string>(); highResSrc = input.required<string>(); highResLoaded = false;}Responsive Images
Section titled “Responsive Images”<!-- srcset for different resolutions --><img src="image-400.jpg" srcset=" image-400.jpg 400w, image-800.jpg 800w, image-1200.jpg 1200w " sizes="(max-width: 600px) 100vw, 50vw" loading="lazy" alt="Responsive image">
<!-- picture element for art direction --><picture> <source media="(max-width: 600px)" srcset="mobile.jpg"> <source media="(max-width: 1200px)" srcset="tablet.jpg"> <img src="desktop.jpg" loading="lazy" alt="Responsive"></picture>Virtual Scrolling with Images
Section titled “Virtual Scrolling with Images”import { ScrollingModule } from '@angular/cdk/scrolling';
@Component({ standalone: true, imports: [ScrollingModule], template: ` <cdk-virtual-scroll-viewport itemSize="200" style="height: 600px"> <div *cdkVirtualFor="let image of images" class="item"> <img [src]="image.url" loading="lazy" width="200" height="200" [alt]="image.alt"> </div> </cdk-virtual-scroll-viewport> `})export class ImageGalleryComponent { images = Array.from({ length: 1000 }, (_, i) => ({ url: `image-${i}.jpg`, alt: `Image ${i}` }));}Image CDN Integration
Section titled “Image CDN Integration”@Pipe({ name: 'imageCdn', standalone: true})export class ImageCdnPipe implements PipeTransform { transform(url: string, width?: number, height?: number): string { const baseUrl = 'https://cdn.example.com'; const params = [];
if (width) params.push(`w=${width}`); if (height) params.push(`h=${height}`);
const query = params.length > 0 ? `?${params.join('&')}` : ''; return `${baseUrl}/${url}${query}`; }}
// Usage<img [src]="'image.jpg' | imageCdn:400:300" alt="Optimized">Background Image Lazy Loading
Section titled “Background Image Lazy Loading”import { Directive, ElementRef, input, AfterViewInit, inject } from '@angular/core';
@Directive({ selector: '[appLazyBg]', standalone: true})export class LazyBackgroundDirective implements AfterViewInit { private el = inject(ElementRef); appLazyBg = input.required<string>();
ngAfterViewInit() { const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { this.el.nativeElement.style.backgroundImage = `url(${this.appLazyBg()})`; observer.unobserve(this.el.nativeElement); } }); });
observer.observe(this.el.nativeElement); }}
// Usage<div [appLazyBg]="'background.jpg'" style="height: 400px"> Content</div>Preload Critical Images
Section titled “Preload Critical Images”@Component({ template: ` <!-- Preload critical images --> <link rel="preload" as="image" href="hero.jpg">
<img ngSrc="hero.jpg" width="1200" height="600" priority alt="Hero"> `})Best Practices
Section titled “Best Practices”- Use
loading="lazy"for below-the-fold images - Use
loading="eager"orpriorityfor above-the-fold - Always specify
widthandheight(prevent layout shift) - Use
NgOptimizedImagefor automatic optimization - Use responsive images (
srcset,sizes) - Use CDN for image optimization
- Use placeholders for better UX
- Virtual scroll for image galleries
- Preload critical images
- Use WebP format when possible
Image Formats
Section titled “Image Formats”<!-- Use modern formats with fallback --><picture> <source srcset="image.avif" type="image/avif"> <source srcset="image.webp" type="image/webp"> <img src="image.jpg" loading="lazy" alt="Image"></picture>
