In Angular's NgOptimizedImage, what is an image loader, how do you configure one, and what stops working without it?
answer
- a function from config to URL
- IMAGE_LOADER token
- the default returns ngSrc unchanged
- srcset, placeholder, loaderParams need it
basics
~20 sAn NgOptimizedImage loader is a function that turns { src, width, height, loaderParams } into an image URL. Provide it with IMAGE_LOADER or a built-in CDN loader provider; without one, no srcset is generated and placeholder can't work.
solid answer
~40 sA loader is a plain function `(config: ImageLoaderConfig) => string`. The directive calls it with `src` (the `ngSrc` value), an optional `width` (for each `srcset` entry), a `height` computed from the aspect ratio, `isPlaceholder`, and your `loaderParams`, and uses the returned URL. You provide one for the app or a component with `{ provide: IMAGE_LOADER, useValue: myLoader }`, or with one of the built-in `provide…Loader(baseUrl)` functions in `@angular/common` for supported image CDNs. The default loader returns `ngSrc` unchanged, so Angular cannot ask for different widths: no automatic `srcset` is generated, `placeholder` set to true throws, and `ngSrcset` or `loaderParams` only produce development warnings.
code
ts · 21 linesimport { ApplicationConfig } from '@angular/core';
import { IMAGE_LOADER, ImageLoaderConfig } from '@angular/common';
function productImageLoader(config: ImageLoaderConfig): string {
const params = new URLSearchParams();
if (config.width) {
params.set('w', String(config.width));
}
if (config.isPlaceholder) {
params.set('q', '20');
}
if (config.loaderParams?.['crop'] === 'square') {
params.set('fit', 'crop');
}
const query = params.toString();
return `https://img.example.com/${config.src}` + (query ? `?${query}` : '');
}
export const appConfig: ApplicationConfig = {
providers: [{ provide: IMAGE_LOADER, useValue: productImageLoader }],
};go deeper
Recall that a loader turns ngSrc and a width into a URL, and that it is provided with IMAGE_LOADER or a built-in provider.
Explain what ImageLoaderConfig carries and which features (srcset, placeholder, ngSrcset, loaderParams) need a real loader.
Write loaders that honour width and placeholders, scope them per subtree, and keep data-URL placeholders tiny.
Choose one image URL contract for the organisation so loaders stay trivial and cacheable across apps.
## What a loader is **`NgOptimizedImage`** does not know how your images are hosted. To request an image at 640 px or 1200 px wide, it needs to know how to express "this file, at this width" as a URL. That knowledge is the **image loader**: a function from `ImageLoaderConfig` to a URL string. `ImageLoaderConfig` (from `@angular/common`) carries: | Field | Meaning | |---|---| | `src` | the value of `ngSrc` | | `width` | requested width in pixels, for srcset entries and placeholders | | `height` | derived from the image's aspect ratio when a width is requested | | `isPlaceholder` | `true` when the request is for the low-resolution placeholder | | `loaderParams` | whatever object you bound with `[loaderParams]` on the image | ## Configuring one **A custom loader** is provided through the **`IMAGE_LOADER`** injection token: ```ts import { ApplicationConfig } from '@angular/core'; import { IMAGE_LOADER, ImageLoaderConfig } from '@angular/common'; export const appConfig: ApplicationConfig = { providers: [ { provide: IMAGE_LOADER, useValue: (config: ImageLoaderConfig) => `https://img.example.com/${config.src}` + (config.width ? `?w=${config.width}` : ''), }, ], }; ``` **Built-in loaders** for several popular image CDNs are exported from `@angular/common` as `provide…Loader(baseUrl)` functions. You pass your base URL and write `ngSrc` values relative to it. When the base URL is a literal string, Angular can also generate a **preconnect** link for that origin automatically. A custom loader **must honour `width`**: if it ignores it, every srcset entry points at the same file and the browser downloads the full-size image regardless. ## What depends on the loader The default loader returns `src` unchanged. Features that need a URL per width therefore fail or degrade without a real loader: 1. **Automatic `srcset`** — skipped entirely; the image has only `src`. 2. **`placeholder` (boolean)** — throws **NG02963**, since the "small" placeholder would be the full image. 3. **`ngSrcset`** — logs NG02963 as a warning; every density or width would be the same URL. 4. **`loaderParams`** — logs NG02963 as a warning; nothing consumes the params. A **data URL placeholder** (`placeholder="data:image/png;base64,…"`) works without a loader, which is why it exists. Keep it small: the directive warns above 4,000 characters and raises an error above 10,000, because the data sits in your bundle or HTML. ## Placeholders in more detail - `placeholder` asks the loader for a tiny version of the image (**30 px wide** by default, configurable with `placeholderResolution` in `IMAGE_CONFIG`) and shows it as a blurred CSS background until the real image loads. - `[placeholderConfig]="{ blur: false }"` shows it without blur. - The directive removes the placeholder background once the image has loaded. ## `loaderParams` `[loaderParams]="{ variant: 'thumb' }"` passes arbitrary data to your loader; it does nothing on its own. It is how one custom loader can serve two image origins (a flag chooses which) or turn on CDN-specific features. The built-in loaders (except one) accept a `transform` entry for CDN transformation syntax. ## Scoping a loader `IMAGE_LOADER` is resolved through dependency injection like any token, so you can provide a different loader for one component subtree, for example user avatars from a separate origin, while the app-wide provider serves product photos.
- Why must a custom loader use config.width?The directive builds `srcset` by calling the loader once per width or density. If the loader ignores `width`, every entry resolves to the same URL, so the browser picks among identical full-size files and the responsive benefit disappears.
- How would you serve images from two different origins with one loader?Pass a flag through `loaderParams`, for example `[loaderParams]="{ origin: 'avatars' }"`, and branch on it inside a custom `IMAGE_LOADER` function. Alternatively, provide a different `IMAGE_LOADER` in the component subtree that renders the second kind of image.
saying these in an interview costs you the question
- The default loader already resizes images for each srcset width.
- A custom loader can ignore width because the browser resizes images.
- placeholder works the same with or without a loader.
- loaderParams changes the URL even without a custom loader.
- IMAGE_LOADER can only be provided once, at the root.