Angular image components, directives, provider configuration, Angular IMAGE_LOADER integration, and SSR middleware.
@desource/image-angular brings the shared Desource Image engine to Angular through standalone components, native-element directives, a callable service, IMAGE_LOADER integration, head preloads, placeholder lifecycle, and lazy IPX middleware for SSR.
The package supports Angular 19, 20, 21, and 22.
Angular's NgOptimizedImage is a strong built-in directive for enforcing image best practices. It can prioritize LCP images, generate srcset, work with Angular image loaders, and warn about common layout problems.
Use Desource Image when your image layer needs more than a single <img> directive:
- You want Angular components and native-element directives from the same package.
- You need first-class
<picture>output with AVIF/WebP sources and fallback control. - You want the same provider, preset, alias, and validation model used by React and Svelte packages.
- You need a larger provider catalog, typed custom providers, or provider behavior inspired by
@nuxt/image. - You want local IPX middleware for Angular SSR instead of writing the optimizer route yourself.
- You want generated placeholders, preload links, and provider URLs from one config object.
- You still want to use
NgOptimizedImage, but with Desource registered as Angular'sIMAGE_LOADER.
If NgOptimizedImage plus one Angular loader already covers your app, keep it. Desource Image is for teams that need Angular-native rendering plus a portable provider model.
Reference: Angular NgOptimizedImage guide.
npm install @desource/image-angularInstall ipx only when the app serves local /_ipx transformations itself:
npm install ipxHosted providers such as Vercel, Netlify, Cloudinary, Imgix, Sanity, or ImageKit do not need the local optimizer. Add @desource/image as a direct dependency only when the application imports core helpers or provider factories directly.
Configuration is optional. With no explicit provider, the shared engine detects Vercel, Netlify, or AWS Amplify from the build environment and falls back to IPX.
Register defaults in app.config.ts:
import { ApplicationConfig } from '@angular/core';
import { provideDsImage } from '@desource/image-angular';
export const appConfig: ApplicationConfig = {
providers: [
provideDsImage({
quality: 80,
screens: { sm: 640, md: 768, lg: 1024 },
domains: ['images.example.com']
})
]
};provideDsImage() also registers Angular's IMAGE_LOADER, so the same configuration can power NgOptimizedImage:
import { NgOptimizedImage } from '@angular/common';
import { Component } from '@angular/core';
@Component({
standalone: true,
imports: [NgOptimizedImage],
template: `<img ngSrc="/img/hero.jpg" alt="Mountain lake" width="1200" height="800" />`
})
export class HeroComponent {}import { ChangeDetectionStrategy, Component } from '@angular/core';
import { DsImageComponent } from '@desource/image-angular';
@Component({
selector: 'app-hero',
standalone: true,
imports: [DsImageComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<ds-image
src="/img/hero.jpg"
alt="Aurora above a mountain lake"
width="1600"
height="900"
sizes="100vw md:50vw lg:800px"
format="webp"
quality="82"
placeholder
[preload]="{ fetchPriority: 'high' }"
class="hero"
(load)="onHeroLoad()"
(error)="onHeroError()"
/>
`
})
export class HeroComponent {
onHeroLoad(): void {}
onHeroError(): void {}
}The host component uses display: contents; the visible node is the generated native <img>. alt is required. Numeric and boolean template attributes are coerced. Signal inputs recompute output when source, modifiers, config, or native attributes change.
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { DsPictureComponent } from '@desource/image-angular';
@Component({
standalone: true,
imports: [DsPictureComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<ds-picture
src="/img/hero.jpg"
alt="Responsive mountain landscape"
width="1600"
height="900"
sizes="100vw md:50vw lg:800px"
[formats]="['avif', 'webp']"
fallbackFormat="jpg"
imgClass="hero"
/>
`
})
export class HeroPictureComponent {}The component renders a native <picture> with ordered <source> elements and a fallback <img>. format="avif,webp", [format]="['avif', 'webp']", and the legacyFormat alias are supported.
Use directives when the template should remain a regular <img> or <picture>:
import { Component } from '@angular/core';
import { DsImageDirective, DsPictureDirective } from '@desource/image-angular';
@Component({
standalone: true,
imports: [DsImageDirective, DsPictureDirective],
template: `
<img
dsImage="/img/card.jpg"
alt="A coastal village"
width="720"
height="480"
densities="1x 2x"
placeholder
(dsLoad)="onImageLoad()"
(dsError)="onImageError()"
/>
<picture
dsPicture="/img/card.jpg"
alt="A coastal village at sunset"
width="720"
height="480"
[formats]="['avif', 'webp']"
fallbackFormat="jpg"
(dsLoad)="onImageLoad()"
>
<img alt="A coastal village at sunset" />
</picture>
`
})
export class CardComponent {
onImageLoad(): void {}
onImageError(): void {}
}Directive outputs use (dsLoad) and (dsError) so they do not collide with native DOM events. Component outputs use (load) and (error).
A dsPicture element must contain one fallback <img>. The directive manages generated <source> elements around that fallback.
sizes accepts a regular browser string, a breakpoint shorthand string, or an object:
<ds-image src="/img/gallery.jpg" alt="Gallery" width="1200" sizes="100vw sm:50vw lg:600px" densities="1x 2x" />sizes = { sm: '100vw', md: '50vw', lg: 600 };The core engine deduplicates candidate widths, clamps them to provider-supported sizes where required, and generates deterministic srcset and sizes attributes.
<ds-image src="/img/hero.jpg" alt="Hero" width="1200" placeholder placeholderClass="blur" />
<ds-image src="/img/hero.jpg" alt="Hero" width="1200" [placeholder]="[48, 32, 25, 8]" />
<ds-image src="/img/hero.jpg" alt="Hero" width="1200" placeholder="data:image/png;base64,..." />placeholdergenerates a small transformed image.- A tuple controls width, height, quality, and blur.
- A string can be a custom URL or data URL.
- The full image is preloaded off-screen and replaces the placeholder only after decode succeeds.
placeholderClassis present only while the placeholder is visible.preloadinserts a responsive<link rel="preload" as="image">into the document head.- Identical preload links are reference-counted and removed during teardown.
prioritysets eager loading and high fetch priority. Usepreloadwhen a head link is also required.
SSR and initial hydration output use the same attributes.
Components expose common native attributes directly, including class, style, id, role, ARIA labels, referrerpolicy, crossorigin, usemap, and data-testid.
Use nativeAttrs for arbitrary image attributes:
<ds-image src="/img/hero.jpg" alt="Hero" [nativeAttrs]="{ 'data-track': 'hero', draggable: false }" />For <ds-picture>, use imgAttrs, imgClass, and imgStyle for the fallback image. Generated attributes such as src, srcset, dimensions, loading, and decoding are owned by the directive/component and cannot be overridden accidentally.
import { inject } from '@angular/core';
import { DsImageService } from '@desource/image-angular';
const $img = inject(DsImageService).create();
const url = $img('/img/hero.jpg', {
width: 800,
format: 'webp',
quality: 75
});
const attrs = $img.getAttrs({
src: '/img/hero.jpg',
alt: 'Hero',
width: 800
});The callable helper also exposes getImage, getSizes, getMeta, getPicture, getPreloadLink, and configured preset shortcuts.
Convenience providers are available for common deployment paths:
import { provideDsAwsAmplifyImage, provideDsIpxImage, provideDsVercelImage } from '@desource/image-angular';
provideDsIpxImage();
provideDsVercelImage();
provideDsAwsAmplifyImage();Register built-in or custom providers from the core package:
import { cloudinaryProvider } from '@desource/image/providers/cloudinary';
import { provideDsImage } from '@desource/image-angular';
provideDsImage({
provider: 'cloudinary',
providers: {
cloudinary: cloudinaryProvider({ cloudName: 'demo' })
},
presets: {
avatar: { width: 96, height: 96, fit: 'cover', quality: 80 }
}
});<ds-image preset="avatar" src="/users/ada.jpg" alt="Ada" />Provider modules and custom-provider utilities are documented in @desource/image.
Place the image middleware before static-file and Angular SSR handlers:
import { createDsImageMiddleware } from '@desource/image-angular/server';
app.use(
createDsImageMiddleware({
dirs: [browserDistFolder],
domains: ['images.example.com'],
maxAge: 60 * 60 * 24 * 30
})
);Keep the Sharp-based optimizer out of the Angular server bundle:
{
"projects": {
"app": {
"architect": {
"build": {
"options": {
"externalDependencies": ["@desource/image-angular/server"]
}
}
}
}
}
}The middleware loads ipx lazily and only handles requests under /_ipx. Local directories resolve from the server process. Remote optimization is denied by default; add trusted domains, or set allowAllDomains: true only for an intentionally public optimizer.
Hosted providers generate platform URLs and should be paired with that platform's image configuration. For example, use the Vercel provider with Vercel image settings, not with the IPX middleware.
pnpm --filter @desource/image-angular build
pnpm --filter @desource/image-angular typecheck
pnpm --filter @desource/image-angular test:unit:coverage
pnpm --filter @desource/image-angular test:e2e
pnpm --filter @desource/image-angular publish:checkMIT © 2026 DeSource Labs