Skip to content

Using NgOptimizedImage With Cloudinary

While Cloudinary provides an extensive Angular SDK, which also includes the CloudinaryImageComponent that you can use to display your Cloudinary images, a little-known fact is that Angular exposes a Cloudinary image loader provider you can use with the NgOptimizedImage directive.

That means if you don’t want to install the Cloudinary Angular SDK, or if you’re an Angular purist and prefer to use Angular’s built-in features as much as possible, you can still conveniently load Cloudinary images using Angular’s img tag and the NgOptimizedImage directive.

The NgOptimizedImage directive in Angular wraps the HTML img element while adhering to best practices for image loading performance, such as automatically setting the fetchpriority attribute on the <img> tag, lazy loading non-priority images by default, generating preconnect links, generating srcset attributes to request images at appropriate sizes based on the viewport, or providing preload hints when using server-side rendering (SSR). It also enforces specifying images with width and height attributes to prevent layout shifts and warns you of incorrect dimensions or potential visual distortion.

The NgOptimizedImage directive can also automatically utilize low-resolution placeholders when using a CDN such as Cloudinary and custom placeholders via base64 data URLs.

To begin, import the directive from @angular/common and include it in your component or module imports:

import { NgOptimizedImage } from '@angular/common';
@Component({
  selector: 'app-component',
  imports: [NgOptimizedImage], // Import the directive
  templateURL: './app.component.html',
})
Code language: JavaScript (javascript)

And then, in your HTML template, you can use the NgOptimizedImage directive by specifying the ngSrc attribute in your img tag:

<img ngSrc="public/my-image.jpg" width="500" height="500" />
Code language: HTML, XML (xml)

If you need the image to load first, you can use the priority attribute:

<img ngSrc="public/my-image.jpg" width="500" height="500" priority />
Code language: HTML, XML (xml)

If you need the image to fill the container, you can use the fill attribute and omit the width and height attributes. The fill mode allows it to behave like a background image, adjusting to the container’s dimensions.

<div style="position: relative; width: 500px; height: 250px">
  <img ngSrc="public/my-image.jpg" fill />
</div>
Code language: HTML, XML (xml)

For more details about the NgOptimizedImage directive, check out the Angular documentation.

As mentioned, you can use the built-in provideCloudinaryLoader function to integrate your Cloudinary instance with your Angular application. To use it, you need to provide the provideCloudinaryLoader function in your application’s providers array:

// app.config.ts
import { cloudinaryCloudName } from "../environments/environment";
import { ApplicationConfig, provideZoneChangeDetection } from "@angular/core";
import { provideRouter } from "@angular/router";

import { routes } from "./app.routes";
import { provideClientHydration } from "@angular/platform-browser";

// Import the provideCloudinaryLoader function
import { provideCloudinaryLoader } from "@angular/common";

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
    provideClientHydration(),
    // Provide the Cloudinary loader with your Cloudinary URL
    provideCloudinaryLoader(`https://res.cloudinary.com/${cloudinaryCloudName}`),
  ],
};
Code language: JavaScript (javascript)

You can then use the NgOptimizedImage directive with the ngSrc attribute to load your Cloudinary images:

// cld-sample.jpg is an image that should exist in your Cloudinary account
<img ngSrc="cld-sample.jpg" width="500" height="500" priority />
Code language: JavaScript (javascript)

You can also add the placeholder attribute to automatically download a low-resolution placeholder image first before the full image is loaded:

<img ngSrc="cld-sample.jpg" width="500" height="500" priority placeholder />
Code language: HTML, XML (xml)

That’s it! You should now see your image loaded when you run your Angular application with ng serve and navigate to localhost:4200. The rendered html should look something like this:

<img
  _ngcontent-ng-c1071307065=""
  ngsrc="cld-sample.jpg"
  width="500"
  height="500"
  priority=""
  placeholder=""
  ng-reflect-ng-src="cld-sample.jpg"
  ng-reflect-width="500"
  ng-reflect-height="500"
  ng-reflect-priority=""
  ng-reflect-placeholder=""
  loading="eager"
  fetchpriority="high"
  ng-img="true"
  src="https://res.cloudinary.com/[my-cloud-name]/image/upload/q_auto/cld-sample.jpg"
  srcset="
    https://res.cloudinary.com/[my-cloud-name]/image/upload/q_auto,w_500/cld-sample.jpg  1x,
    https://res.cloudinary.com/[my-cloud-name]/image/upload/q_auto,w_1000/cld-sample.jpg 2x
  "
  style=""
/>
Code language: HTML, XML (xml)

Angular provides a built-in Cloudinary image loader, but it isn’t widely adopted because of its basic capabilities. The loader’s current implementation lacks the flexibility to fully leverage Cloudinary’s powerful features, such as resizing, transformations, and optimizations, which are key to maximizing the platform’s potential.

The most convenient solution is to use the Cloudinary Angular SDK, which provides a comprehensive set of features and options for working with Cloudinary images.

However, you may still want to use the NgOptimizedImage directive. In that case, you can create a custom image loader that integrates with Cloudinary’s URL API to generate image URLs with the desired transformations.

You can also use some features of the @cloudinary/url-gen SDK to generate the transformation string in your loader based on the specified loaderParams.

First, you’ll need to install the @cloudinary/url-gen package:

npm install @cloudinary/url-gen
Code language: CSS (css)

Then, you can create a custom Cloudinary loader function that generates the Cloudinary URL based on the provided ImageLoaderConfig and loaderParams. Here’s an example of a custom Cloudinary loader function that generates a Cloudinary URL with the specified transformations:

import { cloudinaryCloudName } from "../environments/environment";
import { ImageLoaderConfig } from "@angular/common";
import { transformationStringFromObject } from "@cloudinary/url-gen/index";
import { LegacyTransformation } from "@cloudinary/url-gen/types/types";

// Reexport the type from the Cloudinary SDK for the transformation config
export type TransformationConfig = LegacyTransformation;

export function customCloudinaryLoader(config: ImageLoaderConfig) {
  // Specify your Cloudinary URL here
  const path = `https://res.cloudinary.com/${cloudinaryCloudName}`;

  // For a placeholder image, we use the lowest image setting available to reduce the load time
  // else we use the auto size
  const quality = config.isPlaceholder ? "q_auto:low" : "q_auto";

  let params = `${quality}`;
  if (config.width) {
    params += `,w_${config.width}`;
  }
  if (config.loaderParams && config.loaderParams["transformation"]) {
    // Generate the transformation string from the loaderParams object using the Cloudinary SDK
    params += `,${transformationStringFromObject(
      config.loaderParams["transformation"]
    )}`;
  }

  // Return the Cloudinary URL with the specified transformations
  return `${path}/image/upload/${params}/${config.src}`;
}
Code language: JavaScript (javascript)

You can then provide this custom loader function in your application’s providers array instead of the built-in Cloudinary loader:

import { ApplicationConfig, provideZoneChangeDetection } from "@angular/core";
import { provideRouter } from "@angular/router";

import { routes } from "./app.routes";
import { provideClientHydration } from "@angular/platform-browser";
import { IMAGE_LOADER } from "@angular/common";
import { customCloudinaryLoader } from "./custom-cloudinary-loader";

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
    provideClientHydration(),
    // Provide the custom loader
    {
      provide: IMAGE_LOADER,
      useValue: customCloudinaryLoader,
    },
  ],
};
Code language: JavaScript (javascript)

And specify the transformations you want to apply to the image in your component as the loaderParams object:

import { TransformationConfig } from "./custom-cloudinary-loader";

@Component({
  selector: "app-root",
  standalone: true,
  imports: [RouterOutlet, NgOptimizedImage],
  templateUrl: "./app.component.html",
  styleUrl: "./app.component.scss",
})
export class AppComponent {
  loaderParams: {
    transformation: TransformationConfig;
  } = {
    // Specify the transformations you want to apply to the image as an object
    transformation: [
      { gravity: "face", height: 500, width: 500, crop: "thumb" },
      { radius: 100 },
    ],
  };
}
Code language: JavaScript (javascript)

And finally, use the loaderParams object in your NgOptimizedImage directive:

<img
  ngSrc="cld-sample.jpg"
  width="500"
  height="500"
  priority
  placeholder
  [loaderParams]="loaderParams"
/>
Code language: HTML, XML (xml)

You should now see the transformations applied to your Cloudinary image when you navigate to your Angular application in the browser. The rendered html should look like this:

<img
  _ngcontent-ng-c3913398994=""
  ngsrc="cld-sample.jpg"
  width="500"
  height="500"
  priority=""
  placeholder=""
  ng-reflect-ng-src="cld-sample.jpg"
  ng-reflect-width="500"
  ng-reflect-height="500"
  ng-reflect-priority=""
  ng-reflect-placeholder=""
  ng-reflect-loader-params="[object Object]"
  loading="eager"
  fetchpriority="high"
  ng-img="true"
  src="https://res.cloudinary.com/[my-cloud-name]/image/upload/q_auto,c_thumb,g_face,h_500,w_500/r_100/cld-sample.jpg"
  srcset="
    https://res.cloudinary.com/[my-cloud-name]/image/upload/q_auto,w_500,c_thumb,g_face,h_500,w_500/r_100/cld-sample.jpg  1x,
    https://res.cloudinary.com/[my-cloud-name]/image/upload/q_auto,w_1000,c_thumb,g_face,h_500,w_500/r_100/cld-sample.jpg 2x
  "
  style=""
/>
Code language: HTML, XML (xml)

That way, you can use the NgOptimizedImage directive with Cloudinary images, including transformations, in your Angular application.

The NgOptimizedImage directive in Angular provides a convenient way to optimize image loading performance in your Angular applications. While the built-in Cloudinary image loader is limited, you can create a custom loader function that integrates with Cloudinary’s URL API to generate the image URLs with the desired transformations.

Is the custom loader function worth the effort? In most cases, probably not. The Cloudinary Angular SDK provides a much more comprehensive set of features and options for working with Cloudinary images, and is generally easier to use than creating a custom loader function.

However, this exercise can provide a good learning experience and a deeper understanding of how image loading optimization works in Angular, and if you’re an Angular purist or have another reason not to fully use the Cloudinary SDK, the NgOptimizedImage directive with a custom Cloudinary loader can be a viable option.

Sign up for a free Cloudinary account today to optimize your visual assets. And if you found this post helpful and would like to learn more, feel free to join the Cloudinary Community forum and its associated Discord.

Back to top

Featured Post