Angular - Wait for All Images to Load

paviad

Aviad Pineles

Posted on August 15, 2021

Angular - Wait for All Images to Load

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
paviad
Aviad Pineles

Posted on August 15, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Can a Solo Developer Build a SaaS App?
undefined Can a Solo Developer Build a SaaS App?

November 29, 2024

Angular's New Feature: Signals
javascript Angular's New Feature: Signals

November 29, 2024

Stressed or lucky?
angular Stressed or lucky?

November 29, 2024