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.