Skip to content

Commit

Permalink
fix(common): avoid interacting with a destroyed injector (#47243)
Browse files Browse the repository at this point in the history
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 #47243
  • Loading branch information
pkozlowski-opensource authored and alxhub committed Aug 24, 2022
1 parent 40d8821 commit f9511bf
Showing 1 changed file with 11 additions and 26 deletions.
Expand Up @@ -169,6 +169,9 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
private imgElement: HTMLImageElement = inject(ElementRef).nativeElement;
private injector = inject(Injector);

// a LCP image observer - should be injected only in the dev mode
private lcpObserver = ngDevMode ? this.injector.get(LCPImageObserver) : null;

/**
* Calculate the rewritten `src` once and store it.
* This is needed to avoid repetitive calculations and make sure the directive cleanup in the
Expand Down Expand Up @@ -277,10 +280,12 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
// Monitor whether an image is an LCP element only in case
// the `priority` attribute is missing. Otherwise, an image
// has the necessary settings and no extra checks are required.
invokeLCPImageObserverCallback(
this.injector,
(observer: LCPImageObserver) =>
observer.registerImage(this.getRewrittenSrc(), this.rawSrc));
if (this.lcpObserver !== null) {
const ngZone = this.injector.get(NgZone);
ngZone.runOutsideAngular(() => {
this.lcpObserver!.registerImage(this.getRewrittenSrc(), this.rawSrc);
});
}
}
}
this.setHostAttributes();
Expand Down Expand Up @@ -344,10 +349,8 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {

ngOnDestroy() {
if (ngDevMode) {
if (!this.priority && this._renderedSrc !== null) {
invokeLCPImageObserverCallback(
this.injector,
(observer: LCPImageObserver) => observer.unregisterImage(this._renderedSrc!));
if (!this.priority && this._renderedSrc !== null && this.lcpObserver !== null) {
this.lcpObserver.unregisterImage(this._renderedSrc);
}
}
}
Expand All @@ -373,24 +376,6 @@ function inputToBoolean(value: unknown): boolean {
return value != null && `${value}` !== 'false';
}

/**
* Invokes a function, passing an instance of the `LCPImageObserver` as an argument.
*
* Notes:
* - the `LCPImageObserver` is a tree-shakable provider, provided in 'root',
* thus it's a singleton within this application
* - the process of `LCPImageObserver` creation and an actual operation are invoked outside of the
* NgZone to make sure none of the calls inside the `LCPImageObserver` class trigger unnecessary
* change detection
*/
function invokeLCPImageObserverCallback(
injector: Injector, operation: (observer: LCPImageObserver) => void): void {
const ngZone = injector.get(NgZone);
return ngZone.runOutsideAngular(() => {
const observer = injector.get(LCPImageObserver);
operation(observer);
});
}

/***** Assert functions *****/

Expand Down

0 comments on commit f9511bf

Please sign in to comment.