Skip to content

Commit a946ca9

Browse files
pkozlowski-opensourcealxhub
authored andcommitted
fix(common): avoid interacting with a destroyed injector (angular#47243)
The NgOptimizedImage directive was previously trying to use an already destroyed injector in the ngOnDestroy callback. This fix pre-injects necessery tokens so no injector calls are done in the destroy process. PR Close angular#47243
1 parent 353a8ea commit a946ca9

File tree

1 file changed

+11
-26
lines changed

1 file changed

+11
-26
lines changed

packages/common/src/directives/ng_optimized_image/ng_optimized_image.ts

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
169169
private imgElement: HTMLImageElement = inject(ElementRef).nativeElement;
170170
private injector = inject(Injector);
171171

172+
// a LCP image observer - should be injected only in the dev mode
173+
private lcpObserver = ngDevMode ? this.injector.get(LCPImageObserver) : null;
174+
172175
/**
173176
* Calculate the rewritten `src` once and store it.
174177
* This is needed to avoid repetitive calculations and make sure the directive cleanup in the
@@ -277,10 +280,12 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
277280
// Monitor whether an image is an LCP element only in case
278281
// the `priority` attribute is missing. Otherwise, an image
279282
// has the necessary settings and no extra checks are required.
280-
invokeLCPImageObserverCallback(
281-
this.injector,
282-
(observer: LCPImageObserver) =>
283-
observer.registerImage(this.getRewrittenSrc(), this.rawSrc));
283+
if (this.lcpObserver !== null) {
284+
const ngZone = this.injector.get(NgZone);
285+
ngZone.runOutsideAngular(() => {
286+
this.lcpObserver!.registerImage(this.getRewrittenSrc(), this.rawSrc);
287+
});
288+
}
284289
}
285290
}
286291
this.setHostAttributes();
@@ -344,10 +349,8 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
344349

345350
ngOnDestroy() {
346351
if (ngDevMode) {
347-
if (!this.priority && this._renderedSrc !== null) {
348-
invokeLCPImageObserverCallback(
349-
this.injector,
350-
(observer: LCPImageObserver) => observer.unregisterImage(this._renderedSrc!));
352+
if (!this.priority && this._renderedSrc !== null && this.lcpObserver !== null) {
353+
this.lcpObserver.unregisterImage(this._renderedSrc);
351354
}
352355
}
353356
}
@@ -373,24 +376,6 @@ function inputToBoolean(value: unknown): boolean {
373376
return value != null && `${value}` !== 'false';
374377
}
375378

376-
/**
377-
* Invokes a function, passing an instance of the `LCPImageObserver` as an argument.
378-
*
379-
* Notes:
380-
* - the `LCPImageObserver` is a tree-shakable provider, provided in 'root',
381-
* thus it's a singleton within this application
382-
* - the process of `LCPImageObserver` creation and an actual operation are invoked outside of the
383-
* NgZone to make sure none of the calls inside the `LCPImageObserver` class trigger unnecessary
384-
* change detection
385-
*/
386-
function invokeLCPImageObserverCallback(
387-
injector: Injector, operation: (observer: LCPImageObserver) => void): void {
388-
const ngZone = injector.get(NgZone);
389-
return ngZone.runOutsideAngular(() => {
390-
const observer = injector.get(LCPImageObserver);
391-
operation(observer);
392-
});
393-
}
394379

395380
/***** Assert functions *****/
396381

0 commit comments

Comments
 (0)