Angular NgOptimizedImage and BlurHash
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. See the section bibliography for the reference material consulted while preparing these pages. |
Baseline date: 2026-10-07. NgOptimizedImage is the image directive of @angular/common. It applies the practices of
native HTML images automatically: lazy loading by
default, fetchpriority and preloading for the priority image, a generated srcset, and development-time warnings for
missing dimensions or oversized images. The official guide is the
Angular image optimization guide. The framework itself is covered in
the Angular section. Check the guide for the Angular version you use, since options have
been added in several minor releases.
Basic usage
import { Component } from '@angular/core';
import { NgOptimizedImage } from '@angular/common';
@Component({
selector: 'app-cover',
imports: [NgOptimizedImage],
template: `
<img ngSrc="b81f/cover.jpg" width="640" height="960" sizes="(min-width: 60em) 20vw, 50vw" alt="Cover of Moby-Dick">
`,
})
export class CoverComponent {}
-
Use
ngSrcinstead ofsrc. Do not setsrcas well. -
widthandheightare required unless you usefill, because they reserve the layout space. -
The directive adds
loading="lazy"anddecoding="async"by default. For the LCP image, add thepriorityattribute, which setsfetchpriority="high", turns lazy loading off and (with a loader) adds a preload link in the head during server-side rendering. -
fillmakes the image fill its parent, likeobject-fitandposition: absolute. The parent needsposition: relativeand a size. Use it withsizes. -
sizesplus a loader produces a width-basedsrcset. A fixed-size image gets density descriptors (1x,2x) instead. WithngSrcsetyou list your own widths.
<div style="position: relative; aspect-ratio: 2 / 3">
<img ngSrc="b81f/cover.jpg" fill sizes="(min-width: 60em) 20vw, 50vw" alt="Cover of Moby-Dick">
</div>
<img ngSrc="hero/library.jpg" width="1920" height="1080" priority sizes="100vw" alt="Reading room">
Loaders
A loader turns ngSrc and a width into a URL. Without a loader the directive still adds lazy loading and the layout
attributes but does not generate a srcset. Angular ships loaders for several image CDNs, each provided in the
application configuration with the base URL of the service:
import { ApplicationConfig } from '@angular/core';
import {
provideCloudflareLoader, provideCloudinaryLoader, provideImageKitLoader, provideImgixLoader, provideNetlifyLoader,
} from '@angular/common';
export const appConfig: ApplicationConfig = {
providers: [
provideImgixLoader('https://example.imgix.net'),
// provideCloudinaryLoader('https://res.cloudinary.com/<cloud>'),
// provideImageKitLoader('https://ik.imagekit.io/<id>'),
// provideCloudflareLoader('https://example.com'),
// provideNetlifyLoader(),
],
};
The NgOptimizedImage API reference lists the loader functions and their arguments. Image CDN documentation: Cloudinary, imgix.
A custom loader for your own variants
For the variants produced by the backend (see
responsive images and thumbnails), provide your own
IMAGE_LOADER:
import { IMAGE_LOADER, ImageLoaderConfig } from '@angular/common';
const WIDTHS = [320, 640, 1280];
export const mediaLoaderProvider = {
provide: IMAGE_LOADER,
useValue: (config: ImageLoaderConfig) => {
const requested = config.width ?? WIDTHS[0];
const width = WIDTHS.find((w) => w >= requested) ?? WIDTHS[WIDTHS.length - 1];
return `https://cdn.example.com/covers/${config.src}/w${width}.avif`;
},
};
Here ngSrc carries the image id, and the loader chooses the smallest variant that is at least as wide as the request.
Add the provider to appConfig.providers. The loader decides the format, so the AVIF-with-fallback decision is made by
the backend or CDN (the Accept header, see image formats).
Placeholders
NgOptimizedImage has a placeholder attribute. Set it to true to ask the loader’s CDN for a tiny version of the image,
or give it a data URL. It blurs the placeholder by default, which can be changed with placeholderConfig:
<img ngSrc="b81f/cover.jpg" width="640" height="960" [placeholder]="placeholderUrl" [placeholderConfig]="{ blur: true }" alt="">
When the placeholder is a data URL, Angular recommends keeping it small, about 4 KB or less, because it is inlined in the HTML and the component.
BlurHash with the blurhash package
The Angular wrappers for BlurHash (ng-blurhash, ngx-blurhash, angular-blurhash) are not maintained, so decode directly
with the blurhash package and pass the result as the placeholder.
npm install blurhash
import { Injectable } from '@angular/core';
import { decode } from 'blurhash';
@Injectable({ providedIn: 'root' })
export class BlurhashService {
toDataUrl(hash: string, width = 16, height = 16): string {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const context = canvas.getContext('2d')!;
context.putImageData(new ImageData(decode(hash, width, height), width, height), 0, 0);
return canvas.toDataURL();
}
}
import { Component, computed, inject, input } from '@angular/core';
import { NgOptimizedImage } from '@angular/common';
@Component({
selector: 'app-book-cover',
imports: [NgOptimizedImage],
template: `<img [ngSrc]="cover().id" [width]="cover().width" [height]="cover().height"
[placeholder]="placeholder()" sizes="50vw" [alt]="alt()">`,
})
export class BookCoverComponent {
private readonly blurhash = inject(BlurhashService);
cover = input.required<{ id: string; width: number; height: number; blurhash: string }>();
alt = input('');
// computed() decodes once per hash, not on every change detection
placeholder = computed(() => this.blurhash.toDataUrl(this.cover().blurhash));
}
document and canvas do not exist during server-side rendering, so guard the service with isPlatformBrowser, or
decode on the server with sharp as in
the Next.js page. The computed() signal above decodes once per hash; a
getter would decode again on every change detection. The BlurHash algorithm is in
the algorithm page.