@@ -335,6 +335,18 @@ export class NgOptimizedImage implements OnInit, OnChanges {
335
335
*/
336
336
@Input ( { transform : numberAttribute } ) height : number | undefined ;
337
337
338
+ /**
339
+ * The desired decoding behavior for the image. Defaults to `auto`
340
+ * if not explicitly set, matching native browser behavior.
341
+ *
342
+ * Use `async` to decode the image off the main thread (non-blocking),
343
+ * `sync` for immediate decoding (blocking), or `auto` to let the
344
+ * browser decide the optimal strategy.
345
+ *
346
+ * [Spec](https://html.spec.whatwg.org/multipage/images.html#image-decoding-hint)
347
+ */
348
+ @Input ( ) decoding ?: 'sync' | 'async' | 'auto' ;
349
+
338
350
/**
339
351
* The desired loading behavior (lazy, eager, or auto). Defaults to `lazy`,
340
352
* which is recommended for most images.
@@ -444,6 +456,7 @@ export class NgOptimizedImage implements OnInit, OnChanges {
444
456
) ;
445
457
}
446
458
assertValidLoadingInput ( this ) ;
459
+ assertValidDecodingInput ( this ) ;
447
460
if ( ! this . ngSrcset ) {
448
461
assertNoComplexSizes ( this ) ;
449
462
}
@@ -484,6 +497,7 @@ export class NgOptimizedImage implements OnInit, OnChanges {
484
497
485
498
this . setHostAttribute ( 'loading' , this . getLoadingBehavior ( ) ) ;
486
499
this . setHostAttribute ( 'fetchpriority' , this . getFetchPriority ( ) ) ;
500
+ this . setHostAttribute ( 'decoding' , this . getDecoding ( ) ) ;
487
501
488
502
// The `data-ng-img` attribute flags an image as using the directive, to allow
489
503
// for analysis of the directive's performance.
@@ -581,6 +595,20 @@ export class NgOptimizedImage implements OnInit, OnChanges {
581
595
return this . priority ? 'high' : 'auto' ;
582
596
}
583
597
598
+ private getDecoding ( ) : string {
599
+ if ( this . priority ) {
600
+ // `sync` means the image is decoded immediately when it's loaded,
601
+ // reducing the risk of content shifting later (important for LCP).
602
+ // If we're marking an image as priority, we want it decoded and
603
+ // painted as early as possible.
604
+ return 'sync' ;
605
+ }
606
+ // Returns the value of the `decoding` attribute, defaulting to `auto`
607
+ // if not explicitly provided. This mimics native browser behavior and
608
+ // avoids breaking changes when no decoding strategy is specified.
609
+ return this . decoding ?? 'auto' ;
610
+ }
611
+
584
612
private getRewrittenSrc ( ) : string {
585
613
// ImageLoaderConfig supports setting a width property. However, we're not setting width here
586
614
// because if the developer uses rendered width instead of intrinsic width in the HTML width
@@ -1236,6 +1264,21 @@ function assertValidLoadingInput(dir: NgOptimizedImage) {
1236
1264
}
1237
1265
}
1238
1266
1267
+ /**
1268
+ * Verifies that the `decoding` attribute is set to a valid input.
1269
+ */
1270
+ function assertValidDecodingInput ( dir : NgOptimizedImage ) {
1271
+ const validInputs = [ 'sync' , 'async' , 'auto' ] ;
1272
+ if ( typeof dir . decoding === 'string' && ! validInputs . includes ( dir . decoding ) ) {
1273
+ throw new RuntimeError (
1274
+ RuntimeErrorCode . INVALID_INPUT ,
1275
+ `${ imgDirectiveDetails ( dir . ngSrc ) } the \`decoding\` attribute ` +
1276
+ `has an invalid value (\`${ dir . decoding } \`). ` +
1277
+ `To fix this, provide a valid value ("sync", "async", or "auto").` ,
1278
+ ) ;
1279
+ }
1280
+ }
1281
+
1239
1282
/**
1240
1283
* Warns if NOT using a loader (falling back to the generic loader) and
1241
1284
* the image appears to be hosted on one of the image CDNs for which
0 commit comments