Sunday, May 22, 2022

Respond to Element Resizing in Angular Using the ResizeObserver

In Angular applications, when it comes to responding to changes in an element’s size, web developers have got a couple of choices: a) you can create a Resize Directive or b) you can use the ResizeObserver API. It provides a performant means to monitor an element for changes to its size, with notifications being delivered to the observer each time the size changes. This web development tutorial will cover how to import and work with the ResizeObserver within your Angular applications.

ResizeObserver Basics in Angular

One of the main attributes of responsive design is its responsivity to changes in an element’s size. In years gone by, their actual implementations were all-to-often hacky and/or brittle. Moreover, applications could only monitor changes to the viewport; changes to specific elements’ sizes – especially those that were not a container of some sort – were not available, at least not without jumping through many hoops. Now, thanks to the ResizeObserver, applications can react to changes in the size of any observed element, regardless of what caused the change. The ResizeObserver provides access to the new size of the observed elements as well.

Web developers can instantiate a ResizeObserver like any other object, via the new keyword. Its constructor accepts a callback function that is invoked whenever the size of observed elements change. The callback is passed an array of ResizeObserverEntry objects (one entry per observed element) containing the new dimensions of each element. To start monitoring an element, we call the instance’s observe() method and pass it the element:

const resizeObserver = 
  new ResizeObserver(callbackFunction);
resizeObserver.observe(observedElement);

When we want to cease monitoring we call the unobserve() method, passing in the relevant element:

resizeObserver.unobserve(observedElement);
resizeObserver.disconnect();

Read: Ten Ways to Use Angular JS

Using the ResizeObserver in Angular Applications

As with most Angular libraries, the best way to install it is via npm. The resize-observer-polyfill library is a good choice. It is a polyfill for the Resize Observer API that is based on the MutationObserver. It uses Mutation Events as a fall-back if the former is not supported. The resize-observer-polyfill also handles CSS transitions/animations and can even observe changes caused by dynamic CSS pseudo-classes like :hover. Here’s the installation command:

npm install resize-observer-polyfill --save-dev

To use resize-observer-polyfill in our Angular applications, all we need to do is import the library as follows:

import ResizeObserver from 'resize-observer-polyfill';

Here’s an AppComponent that monitors the Document Body and prints a message to the console every time its size changes:

import { Component, OnDestroy } from "@angular/core";
import ResizeObserver from 'resize-observer-polyfill';

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})
export class AppComponent implements OnDestroy {
  private resizeObserver: ResizeObserver;
  private readonly observedElement = document.body;

  constructor() {
    this.resizeObserver = 
      new ResizeObserver(this.resizeCallback);
    this.resizeObserver.observe(this.observedElement);
  }

  ngOnDestroy(): void {
    this.disconnectResizeObserver();
  }

  private resizeCallback() {
    console.log("callback called");
  }

  private disconnectResizeObserver() {
    this.resizeObserver.unobserve(this.observedElement);
    this.resizeObserver.disconnect();
  }
}

Notice that we disconnect from the ResizeObserver in ngOnDestroy to prevent memory leaks.

Introducing a Debounce to Reduce Callback Function Invocations

If we look at the console output produced by the above code, it becomes readily apparent that our callback function is invoked several times for every change to the Document Body’s size:

Element Resizing in Angular Tutorial

 

Depending on what we do in the callback, we may want to throttle the invocations back a little. This is easily done using a debounce. That’s a special kind of delay related to RxJS Observables whereby the application waits a pre-determined amount of time before notifying subscribers. RxJS is a JavaScript library that is used extensively by Angular developers to manage asynchronous processes of all kinds. Observables are the heart of the library, and are a special kind of object that notifies listeners when it receives data from an asynchronous source. In RxJS, listeners are referred to as subscribers.

In addition to different kinds of observables and subscribers, RxJS has an insane number of methods to manipulate the data on its way to the subscribers. One such method is called debounceTime(). It accepts an argument for the number of milliseconds to wait before moving the data down the pipe. Our problem is that we don’t have an observable; what we do have is an object that invokes a callback method. Although we can’t convert a ResizeObserver into an Observable, we can wrap the former with the latter by extending the Observable class:

class ResizeObservable extends Observable<ResizeObserverEntry[]> {
  constructor(elem: HTMLElement) {
    super(subscriber => {
        const ro = new ResizeObserver(entries => {
            subscriber.next(entries);
        });
  
        // Observe one or multiple elements
        ro.observe(elem);
  
        return function unsubscribe() {
            ro.unobserve(elem);
            ro.disconnect();
        }
    });
  }
}

That will allow us to use the RxJS debounceTime() function to add some delay between callback invocations:

public startObserver() {
  this.resizeObserverSubscription = 
    new ResizeObservable(this.observedElement)
      .pipe(debounceTime(200))
      .subscribe(this.resizeCallback);
}

private resizeCallback() {
  console.log("callback called");
}

Read: RxJS Observables in Angular Primer

Cleaning Up Subscriptions in Angular

We should unsubscribe from the ResizeObservable in ngOnDestroy so that we do not leave it active after the application has terminated; otherwise, we will create a memory leak:

export class AppComponent implements OnDestroy {
  ngOnDestroy(): void {
    if(!this.resizeObserverSubscription.closed) {
      this.resizeObserverSubscription.unsubscribe();
    }
  }
}

There is a demo of the ResizeObserver in action on stackblitz. You can drag the browser window to different widths to see the output in the console. After clicking the Use ResizeObserver with Debounce button you should see less output produced than before.

Resize Elements in Angular

 

Conclusion to Element Resizing in Angular

In this tutorial, we used the ResizeObserver API to respond to changes in an HTML element’s size within an Angular application. With or without a debounce, the ResizeObserver can help make your Angular apps more responsive than ever before.

Read: Graceful RxJS Error Handling

Rob Gravelle
Rob Gravelle
Rob Gravelle resides in Ottawa, Canada, and has been an IT guru for over 20 years. In that time, Rob has built systems for intelligence-related organizations such as Canada Border Services and various commercial businesses. In his spare time, Rob has become an accomplished music artist with several CDs and digital releases to his credit.

Popular Articles

Featured