DEV Community

Aviad Pineles
Aviad Pineles

Posted on

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.

Discussion (0)