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 e7e57482d1b..2f71a4d48a5 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 @@ -439,8 +439,17 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy { /** @nodoc */ ngOnChanges(changes: SimpleChanges) { if (ngDevMode) { - assertNoPostInitInputChange( - this, changes, ['ngSrc', 'ngSrcset', 'width', 'height', 'priority']); + assertNoPostInitInputChange(this, changes, [ + 'ngSrc', + 'ngSrcset', + 'width', + 'height', + 'priority', + 'fill', + 'loading', + 'sizes', + 'disableOptimizedSrcset', + ]); } } @@ -687,12 +696,20 @@ function assertUnderDensityCap(dir: NgOptimizedImage, value: string) { * the directive has initialized. */ function postInitInputChangeError(dir: NgOptimizedImage, inputName: string): {} { + let reason!: string; + if (inputName === 'width' || inputName === 'height') { + reason = `Changing \`${inputName}\` may result in different attribute value ` + + `applied to the underlying image element and cause layout shifts on a page.`; + } else { + reason = `Changing the \`${inputName}\` would have no effect on the underlying ` + + `image element, because the resource loading has already occurred.`; + } return new RuntimeError( RuntimeErrorCode.UNEXPECTED_INPUT_CHANGE, `${imgDirectiveDetails(dir.ngSrc)} \`${inputName}\` was updated after initialization. ` + - `The NgOptimizedImage directive will not react to this input change. ` + - `To fix this, switch \`${inputName}\` a static value or wrap the image element ` + - `in an *ngIf that is gated on the necessary value.`); + `The NgOptimizedImage directive will not react to this input change. ${reason} ` + + `To fix this, either switch \`${inputName}\` to a static value ` + + `or wrap the image element in an *ngIf that is gated on the necessary value.`); } /** diff --git a/packages/common/test/directives/ng_optimized_image_spec.ts b/packages/common/test/directives/ng_optimized_image_spec.ts index 52185ef5c66..572d33057c4 100644 --- a/packages/common/test/directives/ng_optimized_image_spec.ts +++ b/packages/common/test/directives/ng_optimized_image_spec.ts @@ -641,34 +641,58 @@ describe('Image directive', () => { }); const inputs = [ - ['ngSrc', 'new-img.png'], // - ['width', 10], // - ['height', 20], // - ['priority', true] + ['ngSrc', 'new-img.png'], + ['width', 10], + ['height', 20], + ['priority', true], + ['fill', true], + ['loading', true], + ['sizes', '90vw'], + ['disableOptimizedSrcset', true], ]; inputs.forEach(([inputName, value]) => { - it(`should throw if an input changed after directive initialized the input`, () => { - setupTestingModule(); + it(`should throw if the \`${inputName}\` input changed after directive initialized the input`, + () => { + @Component({ + selector: 'test-cmp', + template: `` + }) + class TestComponent { + width = 100; + height = 50; + ngSrc = 'img.png'; + priority = false; + fill = false; + loading = false; + sizes = '100vw'; + disableOptimizedSrcset = false; + } - const template = - ''; - // Initial render - const fixture = createTestComponent(template); - fixture.detectChanges(); + setupTestingModule({component: TestComponent}); - expect(() => { - // Update input (expect to throw) - (fixture.componentInstance as unknown as {[key: string]: unknown})[inputName as string] = - value; - fixture.detectChanges(); - }) - .toThrowError( - 'NG02953: The NgOptimizedImage directive (activated on an element ' + - `with the \`ngSrc="img.png"\`) has detected that \`${inputName}\` was updated ` + - 'after initialization. The NgOptimizedImage directive will not react ' + - `to this input change. To fix this, switch \`${inputName}\` a static value or ` + - 'wrap the image element in an *ngIf that is gated on the necessary value.'); - }); + // Initial render + const fixture = TestBed.createComponent(TestComponent); + fixture.detectChanges(); + + const expectedErrorMessage = // + `NG02953: The NgOptimizedImage directive (.*)? ` + + `has detected that \`${inputName}\` was updated after initialization`; + expect(() => { + // Update input (expect to throw) + (fixture.componentInstance as unknown as + {[key: string]: unknown})[inputName as string] = value; + fixture.detectChanges(); + }).toThrowError(new RegExp(expectedErrorMessage)); + }); }); });