Angular Integration

Learn how to use OPTIMAGE with Angular. Create standalone components, services, pipes, and directives for optimized responsive images.

Introduction

OPTIMAGE integrates seamlessly with Angular. Since OPTIMAGE serves standard image URLs, no additional packages are required. Leverage Angular's component architecture for clean, reusable image handling.

No dependencies required! OPTIMAGE provides standard image URLs that work with any Angular setup - standalone components, NgModules, and SSR with Angular Universal.

Basic Usage

The simplest way to use OPTIMAGE in Angular is with a standard img element:

import { Component } from '@angular/core';

@Component({
  selector: 'app-hero',
  standalone: true,
  template: `
    <img
      src="https://cdn.optimage.com/col_abc123/768/hero-banner.webp"
      [srcset]="srcset"
      sizes="100vw"
      alt="Hero banner"
      loading="lazy"
    />
  `
})
export class HeroComponent {
  srcset = `
    https://cdn.optimage.com/col_abc123/320/hero-banner.webp 320w,
    https://cdn.optimage.com/col_abc123/768/hero-banner.webp 768w,
    https://cdn.optimage.com/col_abc123/1280/hero-banner.webp 1280w
  `;
}

OptimageService

Create a service to centralize URL generation logic:

// services/optimage.service.ts
import { Injectable } from '@angular/core';

export type OptimageSize = 320 | 384 | 448 | 512 | 576 | 672 | 768 | 896 | 1024 | 1152 | 1280;

@Injectable({ providedIn: 'root' })
export class OptimageService {
  private readonly cdnUrl = 'https://cdn.optimage.com';
  private readonly sizes: OptimageSize[] = [
    320, 384, 448, 512, 576, 672, 768, 896, 1024, 1152, 1280
  ];

  getUrl(collectionId: string, slug: string, size: OptimageSize = 768): string {
    return `${this.cdnUrl}/${collectionId}/${size}/${slug}.webp`;
  }

  getSrcset(collectionId: string, slug: string): string {
    return this.sizes
      .map(size => `${this.getUrl(collectionId, slug, size)} ${size}w`)
      .join(', ');
  }

  getImageUrls(collectionId: string, slug: string) {
    return {
      src: this.getUrl(collectionId, slug, 768),
      srcset: this.getSrcset(collectionId, slug),
    };
  }
}

Usage

import { Component, inject } from '@angular/core';
import { OptimageService } from './services/optimage.service';

@Component({
  selector: 'app-product-card',
  standalone: true,
  template: `
    <img
      [src]="imageUrls.src"
      [srcset]="imageUrls.srcset"
      sizes="(max-width: 768px) 100vw, 33vw"
      [alt]="product.name"
      loading="lazy"
      class="w-full h-48 object-cover rounded-lg"
    />
  `
})
export class ProductCardComponent {
  private optimage = inject(OptimageService);

  product = { name: 'Product', slug: 'product-image' };

  imageUrls = this.optimage.getImageUrls('col_products', this.product.slug);
}

OptimizedImage Component

Create a reusable standalone component:

// components/optimized-image.component.ts
import { Component, Input, inject, computed, signal } from '@angular/core';
import { OptimageService } from '../services/optimage.service';

@Component({
  selector: 'app-optimized-image',
  standalone: true,
  template: `
    <img
      [src]="src()"
      [srcset]="srcset()"
      [sizes]="sizes"
      [alt]="alt"
      [loading]="priority ? 'eager' : 'lazy'"
      [decoding]="priority ? 'sync' : 'async'"
      [class]="className"
    />
  `
})
export class OptimizedImageComponent {
  private optimage = inject(OptimageService);

  @Input({ required: true }) collectionId!: string;
  @Input({ required: true }) slug!: string;
  @Input({ required: true }) alt!: string;
  @Input() sizes = '100vw';
  @Input() priority = false;
  @Input() className = '';

  src = computed(() =>
    this.optimage.getUrl(this.collectionId, this.slug)
  );

  srcset = computed(() =>
    this.optimage.getSrcset(this.collectionId, this.slug)
  );
}

Usage

import { Component } from '@angular/core';
import { OptimizedImageComponent } from './components/optimized-image.component';

@Component({
  selector: 'app-gallery',
  standalone: true,
  imports: [OptimizedImageComponent],
  template: `
    <div class="grid grid-cols-3 gap-4">
      @for (image of images; track image.slug; let i = $index) {
        <app-optimized-image
          collectionId="col_gallery"
          [slug]="image.slug"
          [alt]="image.alt"
          sizes="(max-width: 768px) 100vw, 33vw"
          [priority]="i < 3"
          className="w-full aspect-square object-cover rounded-lg"
        />
      }
    </div>
  `
})
export class GalleryComponent {
  images = [
    { slug: 'sunset', alt: 'Beautiful sunset' },
    { slug: 'mountain', alt: 'Mountain view' },
    { slug: 'ocean', alt: 'Ocean waves' },
  ];
}

OptimageUrl Pipe

Create a pipe for simple URL generation in templates:

// pipes/optimage-url.pipe.ts
import { Pipe, PipeTransform, inject } from '@angular/core';
import { OptimageService, OptimageSize } from '../services/optimage.service';

@Pipe({
  name: 'optimageUrl',
  standalone: true,
})
export class OptimageUrlPipe implements PipeTransform {
  private optimage = inject(OptimageService);

  transform(slug: string, collectionId: string, size: OptimageSize = 768): string {
    return this.optimage.getUrl(collectionId, slug, size);
  }
}

@Pipe({
  name: 'optimageSrcset',
  standalone: true,
})
export class OptimageSrcsetPipe implements PipeTransform {
  private optimage = inject(OptimageService);

  transform(slug: string, collectionId: string): string {
    return this.optimage.getSrcset(collectionId, slug);
  }
}

Usage

import { Component } from '@angular/core';
import { OptimageUrlPipe, OptimageSrcsetPipe } from './pipes/optimage-url.pipe';

@Component({
  selector: 'app-avatar',
  standalone: true,
  imports: [OptimageUrlPipe, OptimageSrcsetPipe],
  template: `
    <img
      [src]="user.avatarSlug | optimageUrl:'col_avatars':320"
      [srcset]="user.avatarSlug | optimageSrcset:'col_avatars'"
      sizes="64px"
      [alt]="user.name"
      class="w-16 h-16 rounded-full"
    />
  `
})
export class AvatarComponent {
  user = { name: 'John Doe', avatarSlug: 'john-doe' };
}

Optimage Directive

For maximum flexibility, create a directive that enhances existing img elements:

// directives/optimage.directive.ts
import { Directive, Input, ElementRef, OnInit, inject } from '@angular/core';
import { OptimageService } from '../services/optimage.service';

@Directive({
  selector: 'img[optimage]',
  standalone: true,
})
export class OptimageDirective implements OnInit {
  private el = inject(ElementRef<HTMLImageElement>);
  private optimage = inject(OptimageService);

  @Input({ required: true }) optimage!: string; // collectionId
  @Input({ required: true }) slug!: string;
  @Input() optimageSizes = '100vw';
  @Input() optimagePriority = false;

  ngOnInit() {
    const img = this.el.nativeElement;
    const urls = this.optimage.getImageUrls(this.optimage, this.slug);

    img.src = urls.src;
    img.srcset = urls.srcset;
    img.sizes = this.optimageSizes;
    img.loading = this.optimagePriority ? 'eager' : 'lazy';
  }
}

Usage

import { Component } from '@angular/core';
import { OptimageDirective } from './directives/optimage.directive';

@Component({
  selector: 'app-hero',
  standalone: true,
  imports: [OptimageDirective],
  template: `
    <img
      optimage="col_hero"
      slug="homepage-banner"
      optimageSizes="100vw"
      [optimagePriority]="true"
      alt="Welcome to our site"
      class="w-full h-96 object-cover"
    />
  `
})
export class HeroComponent {}

Using with NgOptimizedImage

You can use Angular's built-in NgOptimizedImage with OPTIMAGE by creating a custom loader:

// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { IMAGE_LOADER, ImageLoaderConfig } from '@angular/common';

const optimageLoader = (config: ImageLoaderConfig) => {
  // Expected src format: "collectionId/slug"
  const [collectionId, slug] = config.src.split('/');
  const width = config.width || 768;
  return `https://cdn.optimage.com/${collectionId}/${width}/${slug}.webp`;
};

export const appConfig: ApplicationConfig = {
  providers: [
    { provide: IMAGE_LOADER, useValue: optimageLoader },
  ],
};

Usage

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

@Component({
  selector: 'app-product',
  standalone: true,
  imports: [NgOptimizedImage],
  template: `
    <img
      ngSrc="col_products/product-photo"
      width="400"
      height="300"
      alt="Product photo"
      priority
    />
  `
})
export class ProductComponent {}

Note: NgOptimizedImage handles srcset generation automatically based on the loader. However, using the custom OptimizedImageComponent gives you more control over the exact sizes generated by OPTIMAGE.

Best Practices

  • 1.Use standalone components: Modern Angular favors standalone components for better tree-shaking.
  • 2.Inject services with inject(): Use the functional inject() API for cleaner code.
  • 3.Use signals: Leverage Angular signals for reactive image URL generation.
  • 4.Set priority for LCP: Use priority for above-the-fold images.