How to Track the Element’s Visibility in Angular

An alternative way of tracking the element’s visibility.

Tracking the element's visibility was not a simple task back in the day. One of the common solutions was to listen to the document scroll event and check the element’s visibility by Element.getBoundingClientRect() . It had two major drawbacks:

  • It was CPU intensive
  • The computation was running in the Main thread, causing a sluggish scroll experience

Fortunately, browser vendors created an alternative that is performant and has a straightforward usage API.

Intersection Observer API

The Intersection Observer API is a way to observe an element's visibility asynchronously. all the computation is done in the background thread, meaning the user experience stays as smooth as possible.

In order to use the Intersection Observer, we need basically two things

  • IntersectionObserver instance
  • The element we want to track

Everything else is a matter of configuration, which you can read in docs.

Let’s imagine we want to track the element with id “product”, and get notified whenever it is visible inside the viewport:

// imagine we have some product element that we want to track
const elementToTrack = document.getElementById("product");

// we are creating intersection observer
// and passing the callback that will be called
// whenever something changes
const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    console.log("Intersecting : ", entry.isIntersecting);
  });
});

observer.observe(elementToTrack);

We are doing the following steps:

  • Getting the product element
  • Creating the observer instance and passing the callback, that will be called on every change
  • We are iterating over the entries list. the entries is a list and not a single object, because you may use the same IntersectionObserver on multiple elements
  • We are logging elements visibility
  • Finally, we call observe method to listen to the changes

Angular approach

Instead of using IntersectionObserver natively, we can create a generic directive that will emit an event on elements visibility change. Here’s how it could look like:

import {
  Directive,
  ElementRef,
  EventEmitter,
  NgZone,
  OnDestroy,
  OnInit,
  Output,
} from '@angular/core';

@Directive({
  selector: '[trackVisibility]',
})
export class TrackVisibilityDirective implements OnInit, OnDestroy {
  observer!: IntersectionObserver;

  @Output()
  visibile = new EventEmitter<boolean>();

  constructor(private el: ElementRef<HTMLElement>, private ngZone: NgZone) {}

  ngOnInit(): void {
    this.ngZone.runOutsideAngular(() => {
      this.observer = new IntersectionObserver((entries) => {
        entries.forEach((e) => {
           this.visibile.emit(e.isIntersecting);
        });
      });
      this.observer.observe(this.el.nativeElement);
    });
  }

  ngOnDestroy(): void {
    this.observer.disconnect();
  }
}

Code is self-explanatory, so I won’t describe it too much, the only thing I’ll note is that we are running Intersection Observer outside the Zone JS. From my experience most of the time I used this directive for monitoring tools like Google Analytics where you don’t want to kick change detection on every observed change, in addition, when you have a huge list to observe. Nevertheless, it’s not a mandatory thing to do, whatever suits you…

Keep in mind, that we are disconnecting from the observer on ngOnDestroy to avoid Memory Leak.

And there you have it. Thank you for reading.

Enjoyed this article?

Share it with your network to help others discover it

Continue Learning

Discover more articles on similar topics