39

I was looking for a solution, but nothing found (only articles about (window:resize), but it is not I am looking for).

How to detect element size change in Angular 2?

<div #myElement (sizeChanged)="callback()" />

I want to use some CSS animations and detect element's height and width changes.

Nickon
  • 9,652
  • 12
  • 64
  • 119

5 Answers5

26

Edit: Modern answer

Modern browsers support the ResizeObserver API. The API has been out for a number of years and is now widely supported. Unfortunately, Edge only supports it now that it's on Chromium. If that matters to you, look at the old answer or use a polyfill.

If you're interested, here goes. You want to create a ResizeObserver object and call observe on it. In this example, the blue block keeps resizing as we cycle the text and padding. The current size of the element is added to the unordered list.

const btn = document.getElementById('btn');
const container = document.getElementById('container');
const output = document.getElementById('output');
btn.onclick = () => {
  if (index > 2) {
    if (container.className === '') {
      container.className = 'high';
    } else {
      container.className = '';
    }
    index = 0;
  }
  container.innerText = values[index++];
}

let index = 0;
const values = [
  'Short',
  'Longer text',
  'Very much longer text of the kind that will fill a large container',
];

function createEntry(text) {
const li = document.createElement('li');
    li.innerText = text;
    output.appendChild(li);
}

let obs = new ResizeObserver(entries => {
  console.log(entries)
  for (let entry of entries) {
    const cr = entry.contentRect;
    createEntry(`Element size: ${cr.width}px x ${cr.height}px`)
  }
});
obs.observe(container);
#container {
  display: inline-block;
  background: lightblue;
}
.high {
  padding: 1rem;
}
<div>
  <button id="btn">Cycle</button>
</div>
<div id="container">Test This</div>
<ul id="output">
</ul>

Original answer

The problem is not even an Angular problem. More generally, how do you detect size changes in any element other than window? There is an onresize event, but this is only triggered for window and there are no other obvious solutions.

The general way that many approach this, is to set an interval of, say, 100ms and check the width and height of the div to detect a change. As horrible as it sounds, this is the most common approach.

From this answer to the more general question, there is a library to do this using only events: http://marcj.github.io/css-element-queries/. Supposedly, it's quite good. You would use the ResizeSensor to get what you're looking for.

Unless of course, you're only expecting the div to resize when the window does. Then onresize is what you're looking for.

Cobus Kruger
  • 8,338
  • 3
  • 61
  • 106
  • Any updates on it? Are better solutions available 1.5 years later using Angular 2-6? – tom Aug 04 '18 at 15:35
  • 1
    Honestly @tom I haven't looked into it at all since. I don't know of any new browser API, but even if such a thing existed you would still be stuck having to support IE11 (it's more popular than Edge and not updated at all). – Cobus Kruger Aug 05 '18 at 17:44
  • 1
    @tom I think the solution by Sufiyan Ansari below is a newer better solution. – gsn1074 Nov 29 '19 at 20:45
  • There is a better solution now: https://web.dev/resize-observer/ – James Hollyer Sep 08 '21 at 00:30
  • Thanks @JamesHollyer for the reminder. I've wanted to update this for a while. – Cobus Kruger Sep 09 '21 at 09:14
23

Detecting a change in any element of the angular component. We can use a ResizeObserver (class from import ResizeObserver from 'resize-observer-polyfill'; ) without library.

here is my implementation:

Import:

import ResizeObserver from 'resize-observer-polyfill';

Implementation:

@ViewChild('divId') //eg: <div #divId><div> 
public agNote: ElementRef; //Element Reference on which the callback needs to be added

/**
   * this will bind resize observer to the target element
   */
  elementObserver() {
    var ro = new ResizeObserver(entries => {
      for (let entry of entries) {
        const cr = entry.contentRect;
        console.log('Element:', entry.target);
        console.log(`Element size: ${cr.width}px x ${cr.height}px`);
        console.log(`Element padding: ${cr.top}px ; ${cr.left}px`);
        console.log($event);

      }
    });

    // Element for which to observe height and width 
    ro.observe(this.agNote.nativeElement);
  }
Sufiyan Ansari
  • 1,780
  • 20
  • 22
  • 4
    If u observing a custom Angular element (for example: Component's host element) don't forget to add 'display: block' style to it. Because its 'inline' by default and dont have proper width/height values. – Evgeniy Zaykov Jul 15 '20 at 03:21
11

For angular users, You can easily apply this code....

Html

<div (resized)="onResized($event)"></div>

Angular

import { Component } from '@angular/core';

// Import the resized event model
import { ResizedEvent } from 'angular-resize-event';

@Component({...})
class MyComponent {
  width: number;
  height: number;

  onResized(event: ResizedEvent) {
    this.width = event.newWidth;
    this.height = event.newHeight;
  }
}

Remember you have to install node module and import it to your app.module.ts file.

Check this link for more: https://www.npmjs.com/package/angular-resize-event

nipun-kavishka
  • 842
  • 12
  • 21
6

Two scenarios must be detected:

  1. The element is modified
  2. The window is resized

Since angular if often modifying the element (adding classes, etc.) it's important to wait until changes are "done". Observables can be used for this.

DEMO: https://stackblitz.com/edit/angular-mutationobserver-example-tmafmw

JS Code:

import { Component ,HostListener, AfterViewInit, ViewChild, ElementRef, OnInit, OnDestroy } from '@angular/core';
import { AppService } from './app.service';
import { Subscription, Observable } from 'rxjs';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';

class HeightAndWidth{
  height:number;    
  width:number;
}

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  public elements: string[];
  public height:number = 0;
  public width:number = 0;

  constructor(private appService: AppService) {
    this.elements = ['an element', 'another element', 'who cares'];
  }

  addElement(): void {
    this.elements.push('adding another');
  }

  removeElement(index: number): void {
    this.elements.splice(index, 1);
  }

  private subscription: Subscription;
  @ViewChild('divToTrackHeightChanges') divToTrackHeightChanges:ElementRef;  

  @HostListener('window:resize', ['$event'])
  onResize(event) {
    this.doDivHeightChange(this.getHeightAndWidthObject());    
  }

  getHeightAndWidthObject():HeightAndWidth{
    const newValues = new HeightAndWidth();
    newValues.height = this.divToTrackHeightChanges.nativeElement.offsetHeight;
    newValues.width = this.divToTrackHeightChanges.nativeElement.offsetWidth;
    return newValues;
  }
  setupHeightMutationObserver() {
    const observerable$ = new Observable<HeightAndWidth>(observer => {
      // Callback function to execute when mutations are observed
      // this can and will be called very often
      const callback = (mutationsList, observer2)=> {
        observer.next(this.getHeightAndWidthObject());
      };
      // Create an observer instance linked to the callback function
      const elementObserver = new MutationObserver(callback);

      // Options for the observer (which mutations to observe)
      const config = { attributes: true, childList: true, subtree: true };
      // Start observing the target node for configured mutations
      elementObserver.observe(this.divToTrackHeightChanges.nativeElement, config);      
    });

    this.subscription = observerable$
      .pipe(
        debounceTime(50),//wait until 50 milliseconds have lapsed since the observable was last sent
        distinctUntilChanged()//if the value hasn't changed, don't continue
      )
      .subscribe((newValues => {
        this.doDivHeightChange(newValues);
      }));
  }

  doDivHeightChange(newValues:HeightAndWidth){
   this.height = newValues.height;
   this.width = newValues.width;
  }

  ngAfterViewInit() {
    this.setupHeightMutationObserver();
    this.doDivHeightChange(this.getHeightAndWidthObject());
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

HTML Code

<div #divToTrackHeightChanges>
    <h1>Here is a div that changes</h1>    
    <span style="width:200px;display:inline-block;" *ngFor="let element of elements; let i = index">
      Element <button (click)="removeElement(i)">Remove</button>
    </span>
    <br/>
    <br/>  
  <button (click)="addElement()">Click me enough times to increase the divs height!</button>
</div>
<div>The div above has a height of  {{height}} and width of {{width}}</div>
<div>RESIZE the window to test as well</div>
buddamus
  • 400
  • 4
  • 8
  • Your code is going to work only in case of window is get resize. What user is looking out for component resize which could happen not only on window resize but also once the component is fully loaded. For example grid component initial height is 100px while loading the rows from the backend and once loaded size is increase to 400px. In this case window resize is not happening only div/component height is getting changed. Here ResizeObeserver is the good choice. – Rahul Tokase Nov 24 '19 at 07:46
-3

A really graceful solution similar to the top answer using RxJS...

HTML

<element-to-watch #ref></element-to-watch>

Component or Directive

export class MyComponentOrDirective implements OnInit, OnDestroy {
  private interval = interval(100);      
  private intervalSub = Subscription;      

  constructor(private ref: ElementRef) {}

  ngOnInit(): void {
    this.watchElementChanges();
  }

  ngOnDestroy(): void {
    if (this.intervalSub) {
      this.intervalSub.unsubscribe();
    }
  }

  private watchElementChanges(): void {
    this.intervalSub.subscribe(() => {
      // drag window around and watch the sizes change
      console.log(this.ref.nativeElement.clientWidth);
      console.log(this.ref.nativeElement.clientHeight);
    });
  }
}
Cody Bentson
  • 117
  • 2
  • 9