A custom NgOptimizedImage srcset can disappear even when the template supplies valid image URLs and disableOptimizedSrcset is enabled. Angular 22.2.1 fixes the initial [srcset] binding in that combination. Upgrade the framework packages together, retain the disable flag when your application owns the candidate URLs, and verify the actual DOM attribute and currentSrc rather than checking the template alone.
The comparison below uses the same component on Angular 22.2.0 and 22.2.1, with a separate URL for each density candidate. It also checks [attr.srcset] as a workaround and generated ngSrcset as a control, so a missing custom attribute can be distinguished from an image-loading failure.
Table of Contents
Restore a custom NgOptimizedImage srcset
Import NgOptimizedImage in the standalone component, provide the candidate string as a component field, and bind it to [srcset]. This is the relevant field and template from the executed fixture:
custom = 'images/signed-a.png?token=one 1x, images/signed-b.png?token=two 2x';
<img id="custom" ngSrc="images/fallback.png" [srcset]="custom"
disableOptimizedSrcset width="100" height="50" loading="eager" />
Here the application already owns both candidate URLs, so Angular’s automatic candidate generation is disabled. ngSrc supplies the fallback, while the custom srcset gives the browser its density choices. The example uses local PNGs and illustrative query tokens; it does not exercise a real signing service, CDN, or authorization policy.
On 22.2.0, the fixture’s [srcset] value did not become a DOM attribute and the browser selected the fallback. On 22.2.1, the full candidate string appeared on the element and Chromium selected the 1x or 2x URL in the corresponding test environment. The patch changes the directive’s handling of this input; it does not repair invalid URLs or generate missing image variants.
Check the installed framework versions before comparing results:
npm ls @angular/common @angular/core @angular/compiler @angular/platform-browser
Use your workspace’s normal Angular update process to align the framework packages on a release containing the correction. The verified comparison is exactly 22.2.0 versus 22.2.1; it is not a claim that a particular older release received a backport. Keep the dependency lockfile with the result so another machine can reproduce the same version.
Use the attribute workaround when an upgrade must wait
For the tested initial render on 22.2.0, bind the attribute explicitly instead of the directive input:
<img id="attribute" ngSrc="images/fallback.png" [attr.srcset]="custom"
disableOptimizedSrcset width="100" height="50" loading="eager" />
Angular writes [attr.srcset] as an attribute binding, which bypasses the intercepted srcset input. That produced the expected DOM string and selected image on both tested versions. Keep disableOptimizedSrcset in this workaround so automatic generation does not compete with the URLs supplied by the application.
Treat the workaround as a narrow response to this binding problem. After upgrading, rerun the same checks before removing it; changing syntax without observing the element can leave a second problem, such as an expired URL, hidden behind a successful build.
Choose whether the application or loader owns the URLs
Use srcset when you already have the complete URL list. A backend may return separately signed candidates whose URLs cannot safely be reconstructed by appending a width parameter. Angular’s ngSrcset serves a different path: it accepts density or width descriptors, and an image loader constructs the corresponding URLs.
<img id="generated" ngSrc="generated" ngSrcset="1x, 2x"
width="100" height="50" loading="eager" />
The control fixture registered this IMAGE_LOADER function, mapping the generated widths to two local files while leaving the custom and fallback paths unchanged:
(config: ImageLoaderConfig) => config.src === 'generated'
? `images/generated-${config.width ?? 100}.png` : config.src
The 100-pixel image width and density descriptors produced 100- and 200-pixel loader requests in the tested control. The template deliberately omits disableOptimizedSrcset here because Angular owns generation. Do not put complete filenames into ngSrcset or expect it to preserve a backend-signed candidate list.
Choose one owner for the candidates. If a signing service supplies the URLs, pass those through unchanged and ensure the fallback remains usable. If your image service supports a width-based loader, use the generated path and let the loader implement the service’s URL contract.
Verify the DOM string and the image the browser chose
Inspect the element after Angular renders and the image finishes loading. In DevTools, this distinguishes the requested fallback from the selected candidate:
const img = document.querySelector('img#custom');
({
srcset: img.getAttribute('srcset'),
fallback: img.getAttribute('src'),
currentSrc: img.currentSrc,
loaded: img.complete && img.naturalWidth > 0
});
In the fixed case, srcset must contain the complete candidate string, including the query parameters and descriptors. currentSrc should name a candidate rather than fallback.png, and loaded should be true. Test fresh browser contexts at the relevant device pixel ratios; the deterministic fixture observed the following results in Chromium 141.0.7390.37:
| Binding | Angular 22.2.0 | Angular 22.2.1 |
|---|---|---|
| [srcset] + disableOptimizedSrcset | Attribute absent; fallback at DPR 1 and 2 | Custom string; signed-a at DPR 1, signed-b at DPR 2 |
| [attr.srcset] + disableOptimizedSrcset | Custom string; signed-a / signed-b | Custom string; signed-a / signed-b |
| ngSrcset + IMAGE_LOADER | Generated string; generated-100 / generated-200 | Generated string; generated-100 / generated-200 |
All four version/DPR runs passed their assertions, covering 12 binding cases. The fixture used Node 24.19.0, pinned original Angular packages, esbuild, and Angular JIT in a real browser. It checked the DOM, currentSrc, loaded image dimensions, and requested local paths; screenshots corroborate those results.
The selected image confirms which URL the browser used; it does not measure LCP or bandwidth. Browser candidate selection can depend on conditions beyond density, and one Chromium fixture does not establish identical choices across all browsers or production networks.
Check the boundaries before shipping the fix
The original custom-binding report dates to 2023, and the correction is included in the Angular 22.2.1 release dated September 30, 2026. Describe this as a fix available in that patch, rather than a regression newly introduced by 22.2.0. The reproduced symptom is specific to the tested custom-input and disable-flag combination.
This matrix tests initial values only. If your application refreshes signed URLs after initialization, test that update separately and observe both the DOM and the subsequent request. A passing initial render does not prove that changing only [srcset] later updates the host attribute.
SSR and priority-image preloads also need their own verification. Inspect the server-rendered img and any preload’s imagesrcset, then compare them with the hydrated DOM and network requests. The browser fixture makes no claim that the patch resolves custom-candidate preload behavior; the independent signed-URL issue includes that broader concern.
For real signed URLs, verify expiry, access policy, and the fallback separately. Avoid logging complete query tokens in ordinary diagnostics, and supply URLs from a trusted application response. In the network panel, a present srcset with a failed image request points you toward the URL or hosting path; a missing attribute points you back toward binding and directive behavior.
Keep the smallest reproduction with the lockfile and rerun it against the production build and your supported browsers. The correction is demonstrated when the intended custom string reaches the element and valid candidates load; whether the page becomes faster requires a separate performance measurement.
References
- Angular 22.2.1 changelog
- Angular fix: forward a custom srcset when optimization is disabled
- Angular guide: image optimization and custom srcsets
- Angular API: NgOptimizedImage
- Angular issue #49335: custom srcset input
- Angular issue #57145: backend-signed candidate URLs and preload concerns
Demo source code: angular-22-2-1-custom-srcset-proof.
Found this useful? Support more practical developer content.