| |

Angular 99 🅰️ Image and Font Optimization with NgOptimizedImage

⏱️ Estimated read: 11 min | |

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

AttributePurposeRequired
ngSrcImage sourceYes
widthIntrinsic or rendered widthYes (unless fill)
heightIntrinsic or rendered heightYes (unless fill)
priorityMark as LCP imageFor LCP images
sizesResponsive breakpointsFor responsive images
ngSrcsetManual srcsetOptional
fillFill modeAlternative to dimensions
loadinglazy, eager, autoOptional
placeholderPlaceholder imageOptional
disableOptimizedSrcsetDisable auto srcsetOptional

Font Optimization Techniques

TechniqueImpact
SubsettingReduces font size
font-display: swapPrevents FOIT
PreloadStarts download earlier
Variable fontsOne file for all weights
Self-hostingRemoves third-party connection

The CDN Loaders

LoaderProvider Function
ImgixprovideImgixLoader(url)
CloudinaryprovideCloudinaryLoader(url)
ImageKitprovideImageKitLoader(url)
DefaultNo 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

PitfallWhy It HappensFix
CLS from imagesMissing width and heightAdd dimensions or use fill
LCP image lazy-loadedMissing priorityAdd priority to LCP image
Wrong image size downloadedNo sizes attributeAdd sizes for responsive images
Font causes layout shiftNo font-display: swapAdd font-display: swap
Large font fileNo subsettingSubset to used glyphs
Multiple font requestsMultiple static weightsUse 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

ItemValue
DirectiveNgOptimizedImage from @angular/common
Source attributengSrc replaces src
Requiredwidth and height (or fill)
LCP imagepriority attribute
Lazy loadingDefault for non-priority images
Responsive imagessizes attribute
Manual srcsetngSrcset attribute
Fill modefill attribute
Placeholderplaceholder attribute
CDN loadersImgix, Cloudinary, ImageKit
Font displayfont-display: swap
Font preload<link rel="preload">
Font subsettingRemove unused glyphs

Key takeaways:

  • NgOptimizedImage enforces image loading best practices automatically. The directive requires width and height to prevent layout shift, lazy-loads non-priority images by default, and sets fetchpriority="high" on the priority image .
  • The priority attribute marks the LCP image. The directive sets loading="eager" and fetchpriority="high" on that image, instructing the browser to download it immediately. All other images receive loading="lazy" .
  • The directive generates a responsive srcset automatically. The sizes attribute tells the browser how wide the image will be, and the directive generates width-descriptor srcset entries from the default breakpoints .
  • The fill attribute is for background-style images. It eliminates the width and height requirement and styles the image to fill its containing element. The parent must have position: relative, fixed, or absolute .
  • CDN loaders enable automatic resizing. The provideImgixLoader and provideCloudinaryLoader functions register a loader that transforms the image URL to request the correct size from the CDN .
  • Font optimization is CSS-based. font-display: swap prevents invisible text, preload starts 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.

Was this helpful?


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!