Nickon
Nickon

Reputation: 10156

Element height and width change detection in Angular 2

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.

Upvotes: 42

Views: 76795

Answers (6)

JimmyTheCode
JimmyTheCode

Reputation: 5784

(2024 answer) I've found a way to subscribe to changes to an element's size without needing to import a plugin/library.

The simplest example I can give is as follows

export class ExpandIfEllipsisComponent implements AfterViewInit, OnDestroy {
  @ViewChild('textEl') textEl: ElementRef;
  observer: ResizeObserver;

  ngAfterViewInit(): void {
    this.setUpResizeObserver();
  }

  ngOnDestroy() {
    this.observer.unobserve(this.textEl.nativeElement);
  }
  
  setUpResizeObserver(): void {
    this.observer = new ResizeObserver(_ => {
      this.useNewSizes();
    });

    this.observer.observe(this.textEl.nativeElement);    
  }

  useNewSizes() {
    const element = this.textEl.nativeElement;
    // Updated size values:
    console.log(element.offsetHeight, element.scrollHeight, element.offsetWidth, element.scrollWidth)
  }
  
}

Then in your html

  <span #textEl>Your text content</span>

It's based on this article, which provides more detail.

Upvotes: 2

Cobus Kruger
Cobus Kruger

Reputation: 8615

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.

Upvotes: 25

Cody Bentson
Cody Bentson

Reputation: 125

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);
    });
  }
}

Upvotes: -4

nipun-kavishka
nipun-kavishka

Reputation: 922

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

Upvotes: 11

buddamus
buddamus

Reputation: 400

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>

Upvotes: 6

Sufiyan Ansari
Sufiyan Ansari

Reputation: 1946

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);
  }

Upvotes: 25

Related Questions