13

Here is my angular2 code.

Template

<div #picker class="slider">
  <div class="slider-track">
    <div #sliderSelectionEl class="slider-selection"></div>
    <div #sliderHandle1 class="slider-handle"></div>
    <div #sliderHandle2 class="slider-handle"></div>
  </div>
  <div #tooltipEl class="tooltip">
    <div class="tooltip-arrow"></div>
    <div #tooltipInner class="tooltip-inner"></div>
  </div>
  <input type="text" class="span2" value="" id="sl2"><br/>
</div>


Component


    import {Component, OnInit, Input, ViewChild, ElementRef, Renderer} from '@angular/core';
    export class SliderComponent implements OnInit {
      @ViewChild('picker') picker: ElementRef;

      constructor(private renderer: Renderer, private el: ElementRef) {

      }

      ngAfterViewInit() {
        this.renderer.setElementClass(this.picker.nativeElement, 'slider-horizontal', true);

        console.log(this.picker.nativeElement.offsetWidth);
        console.log(this.picker.nativeElement.offsetHeight);
      }
    }

.slider-horizontal {
  width: 210px;
  height: 20px;
}

The problem is the printed values are different for each time loading. I guess this issue is due to the browser have not completed loading the div. Do you know what is the solution for this?

Avinash
  • 1,245
  • 11
  • 19
loi mai
  • 321
  • 1
  • 4
  • 11

3 Answers3

8

You can detect size changes by using

MutationObserver

Probably the biggest audience for this new api are the people that write JS frameworks, [...] Another use case would be situations where you are using frameworks that manipulate the DOM and need to react to these modifications efficiently ( and without setTimeout hacks! ).

Here is how you can use it to detect changes in elements :

// select the target node
var target = document.querySelector('#some-id'); // or 

// create an observer instance
var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        console.log(mutation.type);
    });
});

// configuration of the observer:
var config = { attributes: true, childList: true, characterData: true }

// pass in the target node, as well as the observer options
observer.observe(target, config);

// later, you can stop observing
observer.disconnect();

For your case, you could use it inside your ngAfterViewInit and refresh your offsets size. You can be more specific and only detect some mutations, and only then extract your offsets.

more info :

doc: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver

compatibility : https://caniuse.com/#feat=mutationobserver

Demo:

var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
       console.log(mutation);
       if(mutation.attributeName == 'class') // detect class change
          /*
          or if(mutation.target.clientWidth == myWidth)
          */
          showOffset(mutation.target);
          
          observer.disconnect();
    });
});

var config = { attributes: true}
var demoDiv = document.getElementById('demoDiv');
var logs = document.getElementById('logs');

// wait for document state to be complete
if (document.readyState === "complete") {
    ngAfterViewInit();
  }
  
document.onreadystatechange = function () {
  if (document.readyState === "complete") {
    ngAfterViewInit();
  }
}

// observe changes that effects demoDiv + add class
function ngAfterViewInit(){
 observer.observe(demoDiv, config);
  demoDiv.classList.add('slider-horizontal');
}

// show offsetWidth + height. 
// N.B offset width and height will be bigger than clientWidth because I added a border. If you remove the border you'll see 220px,20px

function showOffset(element){
  offsetMessage = "offsetWidth:" + demoDiv.offsetWidth + " offsetHeight: " + demoDiv.offsetHeight;
 console.log(offsetMessage);
  logs.innerHTML = offsetMessage;
}
.slider-horizontal {
  border: 2px solid red;
  width: 210px;
  height: 20px;
  background: grey;
}
<div id='demoDiv'> I am a demo div </div>
<div style="margin-top: 20px;"> logs : <span id='logs' style=" padding: 5px; border: 1px solid black"></span></div>
madjaoue
  • 5,104
  • 2
  • 19
  • 31
  • Please could you include the appropriate type of mutation he should be looking for and I will award the bounty, thank you Mium. Edit: I awarded it anyway, but please do include. – 1984 Jul 10 '18 at 16:54
  • Im sorry, but setTimeout worked for me, this didn't work, why?Im using Angular 7. Why document.readyState === "complete" explicitly called? – tomriddle_1234 Jun 04 '19 at 13:01
5

You have to schedule calls to 'offsetWidth' after rendering cycle, angular executes draw on the end of microtask queue, so you could try setTimeout(..., 0) or run Promise.resolve().then(...) outside of zonejs. Hope it helps.

kemsky
  • 14,727
  • 3
  • 32
  • 51
  • Thanks for the answer, you know you can do setTimeout(() => {}) with no second parameter? – 1984 Jul 04 '18 at 14:10
  • 1
    Didn't know its optional, however i don't mind explicit declaration :) – kemsky Jul 04 '18 at 14:12
  • I'm completely with you, the reason I left it out is because if I set a timer part of my brain thinks I'm waiting for say like 10ms so to me it states that there is zero delay. I've seen people use setTimeout(..., 10) for this purpose and even if it causes no problems the 10ms unaccounted for bugs me... At least it's client-side. :) – 1984 Jul 04 '18 at 14:18
  • 1
    This doesn't seem to work reliably, I've tested it quite a lot. – 1984 Jul 10 '18 at 16:55
0

In order to get correct offset values you can use: ngAfterContentChecked with the AfterContentChecked Interface.

This method is called after every change detection run. So, inside this method use a flag (or counter) and setTimeOut:

if (this.counter <= 10) {
      // this print offsetwidth of my element
      console.log('mm ' + this.container.nativeElement.offsetWidth);


      // setTimeOut allow to run another changedetection 
      // so ngAfterContentChecked will run again
      setTimeout(() => { }, 0);

      //you could use a counter or a flag in order to stop getting the right width
      this.counter++;
    }

Hope it helps! Feel free to comment

dam_89
  • 11
  • 3