Chris Kohler

Navigate back to the homepage

How to use ResizeObserver with Angular

Chris Kohler
February 24th, 2020 · 3 min read

tl;dr

Sometimes we need to execute JavaScript when an element is resized.

Current solutions are based on the viewport dimension, not on element dimensions.

ResizeObserver is a new API which allows us to react to element resizing.

There are a few steps required to use it properly with Angular. You have to make sure:

  • to unobserve on destroy
  • that change detection is triggered

I found it to cumbersome to do it on every component. That’s why I’ve created a library to simplify the usage with Angular. 🚀

✨React to element dimension changes

Many changes in screen size or element size can be handled with pure CSS. But sometimes we need to know when an element is resized and execute some logic in JavaScript.

This is usually implemented with either window.onchange or matchMedia. Both solutions are based on the viewport dimension, not the element dimension.

ResizeObserver ResizeObserver - Web APIs | MDN is a new API to solve exactly this problem. In this article we will have a look at how it works and how we can use this new API together with Angular.

Let’s start with why we need a new API.

💣 What’s the problem with window.onchange?

We are only interested in events where our component changes its width. Unfortunately window.onchange sometimes fires too often or not at all.

onchange fires too often

This happens when the viewport changes but our component doesn’t. Do you see the first window.onresize (colored in red)? We are not interested in this event. Running to much code on every onresize could lead to performance problems.

onresize fires too much

onchange doesn’t fire (but should)

This happens when the viewport doesn’t change but the elements within change.

Examples

  • New elements are added dynamically
  • Elements are collapsed or expanded (e.g. Sidebar)

In the graphic below the viewport doesn’t change and the sidebar gets expanded. The ResizeObserver triggers but the window.onresize doesn’t.

onresize doesnt fire

Now that we know why we need the new ResizeObserver Api we will take a closer look at it.

🚀 ResizeObserver in a nutshell

Here is an example on how to use ResizeObserver to subscribe to a resize event of an element.

You can observe multiple elements with one ResizeObserver. That’s why we have an array of entries.

1const observer = new ResizeObserver(entries => {
2 entries.forEach(entry => {
3 console.log("width", entry.contentRect.width);
4 console.log("height", entry.contentRect.height);
5 });
6});
7
8observer.observe(document.querySelector(".my-element"));

This is how an entry looks like:

1{
2 "target": _div_,
3 "contentRect": {
4 "x": 0,
5 "y": 0,
6 "width": 200,
7 "height": 100,
8 "top": 0,
9 "right": 200,
10 "bottom": 100,
11 "left": 0
12 }
13}

Since we subscribed to an observer, we need to unsubscribe as well:

1const myEl = document.querySelector(".my-element");
2
3// Create observer
4const observer = new ResizeObserver(() => {});
5
6// Add element (observe)
7observer.observe(myEl);
8
9// Remove element (unobserve)
10observer.unobserve(myEl);

That’s ResizeObserver in a nutshell. For a full overview of what you can do with ResizeObserver, check out ResizeObserver - Web APIs | MDN

🏁 Status ResizeObserver

At the time of writing (Feb 2020), ResizeObserver is a EditorsDraft Resize Observer. This means it is still in a very early phase World Wide Web Consortium Process Document

Chrome and Firefox support ResizeObserver, Edge and Safari don’t. A ponyfill is available.

🛠 How to use it with Angular

Let’s create a component which displays its width.

code example

1: Create the component

1@Component({
2 selector: "my-component",
3 template: "{{ width }}px"
4})
5export class MyComponent {
6 width = 500;
7}

2: Add Observer

Now let’s observe the nativeElement of our component and log the current width. Works like a charm (in Chrome and Firefox 😉)

1export class MyComponent implements OnInit {
2 width = 500;
3
4 constructor(private host: ElementRef) {}
5
6 ngOnInit() {
7 const observer = new ResizeObserver(entries => {
8 const width = entries[0].contentRect.width;
9 console.log(width);
10 });
11
12 observer.observe(this.host.nativeElement);
13 }
14}

3: Trigger change detection

If you are following this example you may have tried to bind the width directly to the class property. Unfortunately the template is not rerendered and keeps the initial value.

The reason is that Angular has monkey-patched most of the events but not (yet) ResizeObserver. This means that this callback runs outside of the zone.

We can easily fix that by manually running it in the zone.

1export class MyComponent implements OnInit {
2 width = 500;
3
4 constructor(
5 private host: ElementRef,
6 private zone: NgZone
7 ) {}
8
9 ngOnInit() {
10 const observer = new ResizeObserver(entries => {
11 this.zone.run(() => {
12 this.width = entries[0].contentRect.width;
13 });
14 });
15
16 observer.observe(this.host.nativeElement);
17 }
18}

4: Unobserve on destroy

To prevent memory leaks and to avoid unexpected behaviour we should unobserve on destroy:

1export class MyComponent implements OnInit, OnDestroy {
2 width = 500;
3 observer;
4
5 constructor(
6 private host: ElementRef,
7 private zone: NgZone
8 ) {}
9
10 ngOnInit() {
11 this.observer = new ResizeObserver(entries => {
12 this.zone.run(() => {
13 this.width = entries[0].contentRect.width;
14 });
15 });
16
17 this.observer.observe(this.host.nativeElement);
18 }
19
20 ngOnDestroy() {
21 this.observer.unobserve(this.host.nativeElement);
22 }
23}

Want to try it out? Here is a live example.

5: Protip: Create a stream with RxJS

1export class MyComponent implements OnInit, OnDestroy {
2 width$ = new BehaviorSubject<number>(0);
3 observer;
4
5 constructor(
6 private host: ElementRef,
7 private zone: NgZone
8 ) {}
9
10 ngOnInit() {
11 this.observer = new ResizeObserver(entries => {
12 this.zone.run(() => {
13 this.width$.next(entries[0].contentRect.width);
14 });
15 });
16
17 this.observer.observe(this.host.nativeElement);
18 }
19
20 ngOnDestroy() {
21 this.observer.unobserve(this.host.nativeElement);
22 }
23}

Follow me on 🐦 twitter for more blogposts about Angular and web technologies

☀️ Use ng-resize-observer to simplify the usage of ResizeObserver

💻 https://github.com/ChristianKohler/ng-resize-observer

📦 https://www.npmjs.com/package/ng-resize-observer

  1. Install ng-resize-observer
  2. Import and use the providers
  3. Inject the NgResizeObserver stream
1import { NgModule, Component } from "@angular/core";
2import {
3 ngResizeObserverProviders,
4 NgResizeObserver
5} from "ng-resize-observer";
6
7@Component({
8 selector: "my-component",
9 template: "{{ width$ | async }} px",
10 providers: [...ngResizeObserverProviders]
11})
12export class MyComponent {
13 width$ = this.resize$.pipe(
14 map(entry => entry.contentRect.width)
15 );
16
17 constructor(private resize$: NgResizeObserver) {}
18}

NgResizeObserver is created per component and will automatically unsubscribe when the component is destroyed. It’s a RxJS observable and you can use all operators with it.

Want to try it out? Here is a live example on Stackblitz

Make the web resizable 🙌

ResizeObservers allow us to run code exactly when we need it. I hope I could give you an overview over this new API.

If you want to use it in your Angular application, give ng-resize-observer a try and let me know what you think.

If you liked the article 🙌, spread the word and follow me on twitter for more posts on Angular and web technologies.

More articles from Chris Kohler

Angular Dependency Injection Infographic

A graphical overview over Angular Dependency Injection.

February 3rd, 2020 · 1 min read

Improved Dependency Injection with the new providedIn scopes 'any' and 'platform'

A detailed look into the new scopes for providedIn with Angular 9

December 15th, 2019 · 3 min read
© 2023 Chris Kohler
Link to $https://twitter.com/kohlerchristianLink to $https://github.com/christiankohlerLink to $https://instagram.com/mrchriskohlerLink to $https://www.linkedin.com/in/mr-christian-kohler/