refactor(common): improve the NgOptimizedImage error message related to changing inputs (#47926)

This commit updates the error message thrown by the NgOptimizedImage directive, when it detects a situation
when inputs change after initial rendering.

The list of inputs was also updated to include all inputs added recently.

PR Close #47926
This commit is contained in:
Andrew Kushnir
2022-10-31 12:49:50 -07:00
parent ce8160ecb2
commit ec43991665
2 changed files with 70 additions and 29 deletions
@@ -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.`);
}
/**
@@ -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: `<img
[ngSrc]="ngSrc"
[width]="width"
[height]="height"
[priority]="priority"
[fill]="fill"
[loading]="loading"
[sizes]="sizes"
[disableOptimizedSrcset]="disableOptimizedSrcset"
>`
})
class TestComponent {
width = 100;
height = 50;
ngSrc = 'img.png';
priority = false;
fill = false;
loading = false;
sizes = '100vw';
disableOptimizedSrcset = false;
}
const template =
'<img [ngSrc]="ngSrc" [width]="width" [height]="height" [priority]="priority">';
// 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 <img> 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));
});
});
});