Angular 99 🅰️ Image and Font Optimization with NgOptimizedImage
Images and fonts are the two heaviest static resources in most Angular applications. A single unoptimized hero image can weigh more than the entire JavaScript bundle. A web font that arrives late causes text to flicker and shift. The NgOptimizedImage directive was introduced in Angular 16 to automate image loading best practices, and font optimization follows a separate but equally important set of rules.
The directive is not a replacement for an image CDN. It is an enforcement layer. It requires width and height to prevent layout shift, lazy-loads non-priority images by default, and generates responsive srcset attributes automatically when paired with a CDN loader. For fonts, the tools are CSS-based: font-display, preload, and subsetting.
Key point: NgOptimizedImage prioritizes the Largest Contentful Paint (LCP) image by setting fetchpriority="high" on the priority image and lazy-loading everything else. The directive enforces the best practices that developers forget: dimensions for CLS prevention, lazy loading for off-screen images, and srcset for bandwidth efficiency .
Why Image and Font Optimization Matters
The Core Web Vitals — LCP, CLS, and INP — are directly affected by images and fonts.
The LCP problem. The Largest Contentful Paint is usually an image. If that image is lazy-loaded or served at a size larger than the viewport, the LCP score suffers. NgOptimizedImage solves this by requiring the developer to mark the LCP image with the priority attribute. The directive then sets fetchpriority="high" and loading="eager" on that image, instructing the browser to download it immediately .
The CLS problem. Cumulative Layout Shift happens when the browser does not know the dimensions of an image before it loads. The image arrives, pushes the content below it down, and the layout shifts. NgOptimizedImage prevents this by requiring width and height attributes on every image. The browser reserves the space based on the aspect ratio .
The FOIT/FOUT problem. Web fonts that arrive late cause either a Flash of Invisible Text (FOIT) or a Flash of Unstyled Text (FOUT). Both hurt the user experience and the CLS score. The solution is font-display: swap, which shows a fallback font immediately and swaps to the web font when it arrives .
The subsetting problem. A Korean or Japanese web font can exceed 1 MB because it contains thousands of glyphs. Subsetting removes the glyphs the application does not use, reducing the font to under 100 KB in many cases .
a. Using NgOptimizedImage
The directive is imported from @angular/common and added to the component’s imports array. The src attribute is replaced with ngSrc. The width and height attributes are required .
import { NgOptimizedImage } from '@angular/common';
@Component({
imports: [NgOptimizedImage],
template: `
<img ngSrc="/assets/hero.jpg" width="800" height="600" priority alt="Hero image" />
<img [ngSrc]="logoUrl" width="32" height="32" alt="Logo" />
`,
})
export class AppComponent {}
The priority attribute marks the image as the LCP element. The directive sets fetchpriority="high" and loading="eager" on that image. All other images receive loading="lazy" by default .
For images where the dimensions are not known in advance, the fill attribute is used. The image fills its containing element, which must have position: relative, position: fixed, or position: absolute .
<div style="position: relative; width: 100%; height: 400px;">
<img ngSrc="/assets/background.jpg" fill alt="Background" />
</div>
b. Responsive Images and Srcset
The directive generates a responsive srcset automatically based on the sizes attribute. For a fixed-size image, no sizes is needed — the directive generates a density-based srcset from the width and height .
For a responsive image, the sizes attribute tells the browser how wide the image will be at different viewport sizes. The directive generates width-descriptor srcset entries based on the default breakpoints: [16, 32, 48, 64, 96, 128, 256, 384, 640, 750, 828, 1080, 1200, 1920, 2048, 3840] .
<!-- Image fills 100% of viewport width on mobile, 50% on desktop -->
<img
ngSrc="hero.jpg"
width="1200"
height="600"
sizes="(max-width: 768px) 100vw, 50vw"
alt="Hero"
/>
The ngSrcset attribute allows manual specification of the srcset candidates. The directive infers the image filename from ngSrc and appends the width descriptors .
<img ngSrc="hero.jpg" ngSrcset="100w, 200w, 300w" sizes="50vw" />
To disable automatic srcset generation for a single image, the disableOptimizedSrcset attribute is used .
c. Image Loaders and CDN Integration
A loader tells the directive how to format image URLs. The default loader applies no transformation — the ngSrc value is used as-is. For applications using an image CDN, a loader transforms the URL to request the correct size from the CDN .
Angular provides built-in loaders for Imgix and Cloudinary. The Imgix loader is registered with provideImgixLoader :
import { provideImgixLoader } from '@angular/common';
bootstrapApplication(AppComponent, {
providers: [provideImgixLoader('https://my-app.imgix.net/')],
});
With the loader configured, the directive requests https://my-app.imgix.net/hero.jpg?w=400 for a 400-pixel-wide image. The CDN resizes the image on the fly, and the browser downloads only the bytes it needs .
For other CDNs, a custom loader is implemented. The loader function receives the image name and the requested width, and returns the final URL .
d. Font Optimization
Font optimization is not part of the NgOptimizedImage directive. It is a set of CSS and asset-level techniques.
Subsetting removes the glyphs the application does not use. A Latin-only subset of a web font is a fraction of the size of the full font. For Korean and Japanese fonts, subsetting is essential — it can reduce a 1 MB font to 100 KB .
font-display: swap tells the browser to use a fallback font immediately and swap to the web font when it loads. This prevents invisible text and reduces layout shift .
@font-face {
font-family: 'MyFont';
src: url('/assets/fonts/myfont.woff2') format('woff2');
font-display: swap;
}
Preloading the font file tells the browser to start downloading it earlier. The <link rel="preload"> tag in the document head initiates the download before the CSS that references the font is parsed .
<link rel="preload" href="/assets/fonts/myfont.woff2" as="font" type="font/woff2" crossorigin />
Variable fonts combine multiple weights and styles into a single file. A variable font replaces four or five static font files, reducing the total download size and the number of requests.
Self-hosting fonts instead of loading from Google Fonts removes the third-party connection and the extra DNS lookup. The font files are served from the same origin as the application, which is faster and more reliable .
Complete Example Session
This session demonstrates image and font optimization in a single component.
import { NgOptimizedImage } from '@angular/common';
import { Component } from '@angular/core';
@Component({
selector: 'app-hero',
imports: [NgOptimizedImage],
template: `
<!-- LCP image: priority, explicit dimensions -->
<img
ngSrc="/assets/hero.jpg"
width="1200"
height="600"
priority
sizes="100vw"
alt="Hero banner"
/>
<!-- Below-the-fold image: lazy by default -->
<img
ngSrc="/assets/product.jpg"
width="400"
height="300"
sizes="(max-width: 768px) 100vw, 50vw"
alt="Product"
/>
<!-- Fill mode for background-style images -->
<div style="position: relative; width: 100%; height: 300px;">
<img ngSrc="/assets/pattern.jpg" fill alt="Pattern" />
</div>
`,
styles: [`
/* Font optimization */
@font-face {
font-family: 'Inter';
src: url('/assets/fonts/inter-subset.woff2') format('woff2');
font-display: swap;
font-weight: 100 900;
}
:host {
font-family: 'Inter', system-ui, sans-serif;
}
`],
})
export class HeroComponent {}
The index.html includes the font preload:
<link rel="preload" href="/assets/fonts/inter-subset.woff2" as="font" type="font/woff2" crossorigin />
The application configuration registers the Imgix loader:
import { provideImgixLoader } from '@angular/common';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
providers: [
provideImgixLoader('https://my-app.imgix.net/'),
],
});
Quick Reference
NgOptimizedImage Attributes
| Attribute | Purpose | Required |
|---|---|---|
ngSrc | Image source | Yes |
width | Intrinsic or rendered width | Yes (unless fill) |
height | Intrinsic or rendered height | Yes (unless fill) |
priority | Mark as LCP image | For LCP images |
sizes | Responsive breakpoints | For responsive images |
ngSrcset | Manual srcset | Optional |
fill | Fill mode | Alternative to dimensions |
loading | lazy, eager, auto | Optional |
placeholder | Placeholder image | Optional |
disableOptimizedSrcset | Disable auto srcset | Optional |
Font Optimization Techniques
| Technique | Impact |
|---|---|
| Subsetting | Reduces font size |
font-display: swap | Prevents FOIT |
| Preload | Starts download earlier |
| Variable fonts | One file for all weights |
| Self-hosting | Removes third-party connection |
The CDN Loaders
| Loader | Provider Function |
|---|---|
| Imgix | provideImgixLoader(url) |
| Cloudinary | provideCloudinaryLoader(url) |
| ImageKit | provideImageKitLoader(url) |
| Default | No provider needed |
Best Practices
✅ Do This:
<!-- Mark the LCP image with priority -->
<img ngSrc="hero.jpg" width="1200" height="600" priority alt="Hero" /> <!-- ✅ -->
<!-- Use fill mode for background-style images -->
<img ngSrc="bg.jpg" fill alt="Background" /> <!-- ✅ -->
/* Use font-display: swap */
@font-face { font-family: 'MyFont'; src: url(...); font-display: swap; } /* ✅ */
<!-- Preload the web font -->
<link rel="preload" href="/fonts/myfont.woff2" as="font" crossorigin /> <!-- ✅ -->
❌ Don’t Do This:
<!-- Don't omit width and height -->
<img ngSrc="hero.jpg" /> <!-- causes CLS --> <!-- ❌ -->
<!-- Don't mark every image as priority -->
<img ngSrc="a.jpg" priority /> <img ngSrc="b.jpg" priority /> <!-- ❌ -->
/* Don't use the default font-display */
@font-face { font-family: 'MyFont'; src: url(...); } /* ❌ */
<!-- Don't load fonts from third-party origins without preconnect -->
<link href="https://fonts.googleapis.com/css?family=..." rel="stylesheet" /> <!-- ❌ -->
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| CLS from images | Missing width and height | Add dimensions or use fill |
| LCP image lazy-loaded | Missing priority | Add priority to LCP image |
| Wrong image size downloaded | No sizes attribute | Add sizes for responsive images |
| Font causes layout shift | No font-display: swap | Add font-display: swap |
| Large font file | No subsetting | Subset to used glyphs |
| Multiple font requests | Multiple static weights | Use a variable font |
Real-World Examples
1. LCP Image with Priority
<img ngSrc="hero.jpg" width="1200" height="600" priority alt="Hero" />
2. Responsive Image
<img ngSrc="product.jpg" width="400" height="300" sizes="(max-width: 768px) 100vw, 50vw" />
3. Fill Mode
<img ngSrc="bg.jpg" fill alt="Background" />
4. Manual Srcset
<img ngSrc="hero.jpg" ngSrcset="100w, 200w, 300w" sizes="50vw" />
5. Font Preload
<link rel="preload" href="/fonts/inter.woff2" as="font" crossorigin />
6. Font Display Swap
@font-face { font-family: 'Inter'; src: url(...); font-display: swap; }
7. Imgix Loader
provideImgixLoader('https://my-app.imgix.net/')
8. Cloudinary Loader
provideCloudinaryLoader('https://res.cloudinary.com/my-app/')
9. Disable Srcset
<img ngSrc="about.jpg" disableOptimizedSrcset />
10. Placeholder
<img ngSrc="hero.jpg" width="1200" height="600" placeholder />
Visual
NgOptimizedImage Decision Flow
┌──────────────────────────────────────────────┐
│ IS THIS THE LCP IMAGE? │
│ │ │
│ ├─ YES → Add priority │
│ │ └─ fetchpriority="high" │
│ │ └─ loading="eager" │
│ │ │
│ └─ NO → Lazy by default │
│ └─ loading="lazy" │
│ │
│ IS THE SIZE KNOWN? │
│ │ │
│ ├─ YES → Add width and height │
│ │ │
│ └─ NO → Use fill mode │
│ │
└──────────────────────────────────────────────┘
The Srcset Generation
┌──────────────────────────────────────────────┐
│ FIXED SIZE (no sizes attribute): │
│ srcset="image-400w.jpg 1x, image-800w.jpg 2x"│
│ │
│ RESPONSIVE (sizes="100vw"): │
│ srcset="image-640w.jpg 640w, │
│ image-1080w.jpg 1080w, ..." │
│ │
│ The browser picks the closest size to the │
│ rendered width, accounting for DPR. │
│ │
└──────────────────────────────────────────────┘
Font Loading Timeline
┌──────────────────────────────────────────────┐
│ WITHOUT font-display: swap │
│ └─ FOIT: invisible text until font loads │
│ │
│ WITH font-display: swap │
│ ├─ Fallback font renders immediately │
│ └─ Web font swaps in when loaded │
│ │
│ WITH preload: │
│ └─ Font download starts earlier │
│ └─ Swap happens faster │
│ │
└──────────────────────────────────────────────┘
The Resource Layer
┌──────────────────────────────────────────────┐
│ STATIC RESOURCES │
│ │
│ Images: │
│ ├─ NgOptimizedImage (ngSrc) │
│ ├─ priority for LCP │
│ ├─ srcset for responsiveness │
│ └─ CDN loader for resizing │
│ │
│ Fonts: │
│ ├─ Subsetting │
│ ├─ font-display: swap │
│ ├─ preload │
│ └─ Self-hosting │
│ │
└──────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Directive | NgOptimizedImage from @angular/common |
| Source attribute | ngSrc replaces src |
| Required | width and height (or fill) |
| LCP image | priority attribute |
| Lazy loading | Default for non-priority images |
| Responsive images | sizes attribute |
| Manual srcset | ngSrcset attribute |
| Fill mode | fill attribute |
| Placeholder | placeholder attribute |
| CDN loaders | Imgix, Cloudinary, ImageKit |
| Font display | font-display: swap |
| Font preload | <link rel="preload"> |
| Font subsetting | Remove unused glyphs |
Key takeaways:
NgOptimizedImageenforces image loading best practices automatically. The directive requireswidthandheightto prevent layout shift, lazy-loads non-priority images by default, and setsfetchpriority="high"on the priority image .- The
priorityattribute marks the LCP image. The directive setsloading="eager"andfetchpriority="high"on that image, instructing the browser to download it immediately. All other images receiveloading="lazy". - The directive generates a responsive
srcsetautomatically. Thesizesattribute tells the browser how wide the image will be, and the directive generates width-descriptorsrcsetentries from the default breakpoints . - The
fillattribute is for background-style images. It eliminates thewidthandheightrequirement and styles the image to fill its containing element. The parent must haveposition: relative,fixed, orabsolute. - CDN loaders enable automatic resizing. The
provideImgixLoaderandprovideCloudinaryLoaderfunctions register a loader that transforms the image URL to request the correct size from the CDN . - Font optimization is CSS-based.
font-display: swapprevents invisible text,preloadstarts the download earlier, and subsetting removes unused glyphs. Self-hosting removes the third-party connection . - The combination of image and font optimization directly improves Core Web Vitals. LCP improves when the priority image loads immediately. CLS improves when dimensions are reserved. INP is not directly affected by images, but the overall resource load is lighter.
Remember: Images and fonts are the heaviest static resources in an Angular application. NgOptimizedImage automates the image best practices: dimensions for CLS, lazy loading for off-screen images, fetchpriority for the LCP image, and srcset for responsiveness. Font optimization is a separate discipline: subsetting, font-display: swap, and preload. The two together determine the application’s Core Web Vitals scores. Use the directive. Optimize the fonts. The performance is in the details.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!