Angular - Wait for All Images to Load
Aviad Pineles
Posted on August 15, 2021
I was recently engaged in a long discussion on StackOverflow with some programmer who was having a problem. They wanted to run some process which was sensitive to the particular sizes and locations of the images on the page, so they had to find a way to wait for all the images to be done loading, before proceeding.
The naïve solution that was considered, was to subscribe to the window.load
event, which fires when all images are loaded on initial page load. This would work only for the initial load of the Angular page, but thereafter, navigating within the app would not cause a page reload and there would be no obvious way to detect when new images were being added to the page.
The actual solution I came up with involved creating an Angular directive and a service. The directive attaches to all <img>
tags, and subscribes to their load
and error
events, and the service coordinates and keeps track of all the events, and maintains a running list of images that are still being loaded.
Here's the directive:
@Directive({
selector: 'img'
})
export class MyImgDirective {
constructor(private el: ElementRef,
private imageService: ImageService) {
imageService.imageLoading(el.nativeElement);
}
@HostListener('load')
onLoad() {
this.imageService.imageLoadedOrError(this.el.nativeElement);
}
@HostListener('error')
onError() {
this.imageService.imageLoadedOrError(this.el.nativeElement);
}
}
Here's the service:
@Injectable({
providedIn: 'root'
})
export class ImageService {
private _imagesLoading = new Subject<number>();
private images: Map<HTMLElement, boolean> = new Map();
private imagesLoading = 0;
imagesLoading$ = this._imagesLoading.asObservable();
imageLoading(img: HTMLElement) {
if (!this.images.has(img) || this.images.get(img)) {
this.images.set(img, false);
this.imagesLoading++;
this._imagesLoading.next(this.imagesLoading);
}
}
imageLoadedOrError(img: HTMLElement) {
if (this.images.has(img) && !this.images.get(img)) {
this.images.set(img, true);
this.imagesLoading--;
this._imagesLoading.next(this.imagesLoading);
}
}
}
A brief recap of the mechanism, a directive attaches to all <img>
tags, and registers them with the service, while listening to the load
and error
events. The service increments a counter every time a new image is registered with it, and decrements the counter whenever the directive tells it that the image has finished loading (or reached an error state, in case of a broken image link). Every time the counter changes, the service emits the number of loading images, and the consuming component subscribes to that observable and knows that all images have finished loading when it sees a value of 0.
Posted on August 15, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.