0

In component i have some snippet of code

  isLoaded($event) {
    console.log($event);
    this.visible = $event;
    console.log(this.visible);
    this.onClick();
  }

  onClick() {
    this.listImage = this.imageService.getImage();
    let span = document.createElement('span');
    span.innerHTML = ['<img class="thumb" src="', this.listImage[0],
      '" title="', '"/>'
    ].join('');
    document.getElementById('previewImg').insertBefore(span, null);
  }

In html code I have

<div *ngIf="visible">
<div id='previewImg' class="img_content"></div>

$event return true <=> visible = true, but i have error

 Cannot read property 'insertBefore' of null

without *ngIf site render fine.

Why is this happening?

Walter White
  • 976
  • 4
  • 12
  • 29
  • That error message suggests that `document.getElementById('previewImg')` is returning null (i.e. it does not match an exiting DOM node at runtime). – Terry Aug 13 '17 at 18:07
  • @Terry why when i delete ngIf matching element is found? – Walter White Aug 13 '17 at 18:10
  • Maybe because the expression `visible` evaluates to false-y, so that `ngIf` does not render the element. Check what `visible` is giving you in angular. – Terry Aug 13 '17 at 18:13
  • @Terry In html i use string interpolation {{visible}} and is true – Walter White Aug 13 '17 at 18:20
  • 1
    Then you have a race condition: even when `visible` evaluates to true, the DOM has not been updated/rendered yet and `this.onClick()` has already been fired. Use `setTimeout(..., 0)` to move the insertBefore statement to the end of the call stack: https://stackoverflow.com/questions/37355768/how-to-check-whether-ngif-has-taken-effect – Terry Aug 13 '17 at 18:27
  • @Terry I use setTimeout(document.getElementById('previewImg').insertBefore(span, null), 0); but don't help. – Walter White Aug 13 '17 at 18:43
  • 1
    Uhhhh, you have to wrap that in a function call, i.e. `setTimeout(function() { ... }, 0)` please check the link I gave you. – Terry Aug 13 '17 at 18:44
  • @Terry Thanks for attention and help it's work. – Walter White Aug 13 '17 at 18:47

1 Answers1

1

As per my comment, your issue arises because of a race condition: when the visible property resolves to a truthy value, the ngIf directive will cause the nested DOM node to render. However, this rendering is not completed when this.onClick() is called, meaning that the DOM nodes within in will return null when queried.

In other words, your document.getElementById('previewImg') will return null, and this is the source of the error.

To circumvent this, you will have to ensure that the selector is executed at the end of the call stack: this can simply be done by using window.setTimeout(fn, 0), e.g.:

onClick() {

    // Other logic can happen since they do not depend on the updated DOM
    this.listImage = this.imageService.getImage();
    let span = document.createElement('span');
    span.innerHTML = ['<img class="thumb" src="', this.listImage[0],'" title="', '"/>'].join('');

    // Query at end of callstack
    window.setTimeout(function() {
        document.getElementById('previewImg').insertBefore(span, null);
    }, 0);
}
Terry
  • 63,248
  • 15
  • 96
  • 118
  • Or add (load) event on img and put inserBefore() inside? – Vega Aug 13 '17 at 19:05
  • @Vega This has nothing to do with the load state of the reference image. – Terry Aug 13 '17 at 19:10
  • Ok, this is question question. I thought the issue here was that the image is loaded after the new DOM element is added. Putting the addition in (load) would run it after the image is loaded. So what is wrong ? (I don't pretend I am right!) – Vega Aug 13 '17 at 19:13
  • @Vega (1) It's just unnecessary logic. Why use the overhead of an image loading checker when you can simply push the `insertBefore()` statement to end of call stack? (2) If you toggle the same images on and off, it is only loaded once and will not re-fire again – Terry Aug 13 '17 at 19:21
  • I went back to the question and saw it... Thank you – Vega Aug 13 '17 at 19:33