diff --git a/goldens/public-api/common/errors.md b/goldens/public-api/common/errors.md
index 92b257c65a9..f34aa7d1f34 100644
--- a/goldens/public-api/common/errors.md
+++ b/goldens/public-api/common/errors.md
@@ -19,6 +19,8 @@ export const enum RuntimeErrorCode {
// (undocumented)
NG_FOR_MISSING_DIFFER = -2200,
// (undocumented)
+ NGSRCSET_WITHOUT_LOADER = 2963,
+ // (undocumented)
OVERSIZED_IMAGE = 2960,
// (undocumented)
PARENT_NG_SWITCH_NOT_FOUND = 2000,
diff --git a/packages/common/src/directives/ng_optimized_image/ng_optimized_image.ts b/packages/common/src/directives/ng_optimized_image/ng_optimized_image.ts
index aa760ec9f8e..78fd7ab23bb 100644
--- a/packages/common/src/directives/ng_optimized_image/ng_optimized_image.ts
+++ b/packages/common/src/directives/ng_optimized_image/ng_optimized_image.ts
@@ -385,6 +385,7 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
assertNoComplexSizes(this);
}
assertNotMissingBuiltInLoader(this.ngSrc, this.imageLoader);
+ assertNoNgSrcsetWithoutLoader(this, this.imageLoader);
if (this.priority) {
const checker = this.injector.get(PreconnectLinkChecker);
checker.assertPreconnect(this.getRewrittenSrc(), this.ngSrc);
@@ -953,3 +954,17 @@ function assertNotMissingBuiltInLoader(ngSrc: string, imageLoader: ImageLoader)
}
}
}
+
+/**
+ * Warns if ngSrcset is present and no loader is configured (i.e. the default one is being used).
+ */
+function assertNoNgSrcsetWithoutLoader(dir: NgOptimizedImage, imageLoader: ImageLoader) {
+ if (dir.ngSrcset && imageLoader === noopImageLoader) {
+ console.warn(formatRuntimeError(
+ RuntimeErrorCode.NGSRCSET_WITHOUT_LOADER,
+ `${imgDirectiveDetails(dir.ngSrc)} the \`ngSrcset\` attribute is present but ` +
+ `no image loader is configured (i.e. the default one is being used), ` +
+ `which would result in the same image being used for all configured sizes. ` +
+ `To fix this, provide a loader or remove the \`ngSrcset\` attribute from the image.`));
+ }
+}
diff --git a/packages/common/src/errors.ts b/packages/common/src/errors.ts
index d088c0bc160..a886c284f2d 100644
--- a/packages/common/src/errors.ts
+++ b/packages/common/src/errors.ts
@@ -33,4 +33,5 @@ export const enum RuntimeErrorCode {
OVERSIZED_IMAGE = 2960,
TOO_MANY_PRELOADED_IMAGES = 2961,
MISSING_BUILTIN_LOADER = 2962,
+ NGSRCSET_WITHOUT_LOADER = 2963,
}
diff --git a/packages/common/test/directives/ng_optimized_image_spec.ts b/packages/common/test/directives/ng_optimized_image_spec.ts
index cd066fa4fae..5b21e14d6fa 100644
--- a/packages/common/test/directives/ng_optimized_image_spec.ts
+++ b/packages/common/test/directives/ng_optimized_image_spec.ts
@@ -1183,6 +1183,24 @@ describe('Image directive', () => {
expect(consoleWarnSpy.calls.count()).toBe(0);
});
+ it('should warn if there is no image loader but `ngSrcset` is present', () => {
+ setUpModuleNoLoader();
+
+ const template = ``;
+ const fixture = createTestComponent(template);
+ const consoleWarnSpy = spyOn(console, 'warn');
+ fixture.detectChanges();
+
+ expect(consoleWarnSpy.calls.count()).toBe(1);
+ expect(consoleWarnSpy.calls.argsFor(0)[0])
+ .toBe(
+ 'NG02963: The NgOptimizedImage directive (activated on an
element ' +
+ 'with the `ngSrc="img.png"`) has detected that the `ngSrcset` attribute is ' +
+ 'present but no image loader is configured (i.e. the default one is being used), ' +
+ `which would result in the same image being used for all configured sizes. ` +
+ 'To fix this, provide a loader or remove the `ngSrcset` attribute from the image.');
+ });
+
it('should set `src` using the image loader provided via the `IMAGE_LOADER` token to compose src URL',
() => {
const imageLoader = (config: ImageLoaderConfig) => `${IMG_BASE_URL}/${config.src}`;
diff --git a/packages/core/test/bundling/image-directive/e2e/oversized-image/oversized-image.ts b/packages/core/test/bundling/image-directive/e2e/oversized-image/oversized-image.ts
index 920758bc58c..fa03e4416a5 100644
--- a/packages/core/test/bundling/image-directive/e2e/oversized-image/oversized-image.ts
+++ b/packages/core/test/bundling/image-directive/e2e/oversized-image/oversized-image.ts
@@ -6,13 +6,19 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {NgOptimizedImage} from '@angular/common';
+import {IMAGE_LOADER, ImageLoaderConfig, NgOptimizedImage} from '@angular/common';
import {Component} from '@angular/core';
+const imageLoader = {
+ provide: IMAGE_LOADER,
+ useFactory: () => (config: ImageLoaderConfig) => config.src
+};
+
@Component({
selector: 'oversized-image-passing',
standalone: true,
imports: [NgOptimizedImage],
+ providers: [imageLoader],
template: `