Reputation: 10156
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
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
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
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
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
Reputation: 400
Two scenarios must be detected:
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
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