3

I need to change the innerHTML of an element and I've decided to create an attribute directive to accomplish this task. I have created the following example to illustrate what I'm basically trying to do:

StackBlitz

I have used interpolation to set the content of a paragraph:

<p changeContentDirective>{{content}}</p>

The 'changeContentDirective' modifies the innerHTML of the paragraph if the use hovers over it.

this.el.nativeElement.innerHTML = 'modified by directive';

I had also set a function to execute after 5s that will change the value of the 'content' property:

ngOnInit() {
    setTimeout(() => {
      console.log('timeout: modify content');
      this.content = 'modifed after timeout'
    }, 5000);
  }

If I don't hover over the paragraph, the content is being changed after 5s. The strange part is that the content is not modified by the handler from setTimeout if I hover over the element (obviously before the handler gets executed)

I'm not sure why this is happening, it seems that after I modify the innerHTML programmatically, changing the 'content' property from the AppComponent doesn't have any effect on the html displayed by the paragraph.

In the actual application I'm working on, the structure is more complex, I don't have to modify the innerHTML of a paragraph, but the innerHTML of different td's that belong to a table (Kendo Grid), because of this I don't think I can use a pipe.

My question is whether I can modify the innerHTML of an element in a directive by accessing the native element and also why this strange issue occurs?

Thanks in advance

usrrdu
  • 43
  • 1
  • 1
  • 5
  • 1
    Don't modify the DOM directly. Change the value of the `content` variable and let Angular do the work of changing the DOM. Bypassing the framework and making changes it doesn't know about rarely works out well -- when you overwrite parts of the DOM you're also overwriting bindings that Angular is depending on. – Daniel Beck Jan 22 '19 at 20:33

1 Answers1

6

The issue occurs because the innerHTML of this element contains the Angular interpolation.
When you overwrite this innerHTML, the interpolated value of content gets lost.

You can change what gets displayed in the paragraph by changing

this.el.nativeElement.innerHTML = 'modified by directive';

to

this.el.nativeElement.innerText = 'modified by directive';

See this stackblitz for reference.
EDIT: Thanks to @DanielBeck for finding out that this result only works in the Chrome browser.

This technique is bad practice though, as there are better ways to change the innerHTML of an element (e.g. described here).

mahalde
  • 709
  • 5
  • 19
  • changing the innerText will cause exactly the same issue as changing the innerHTML. – Daniel Beck Jan 22 '19 at 20:37
  • 1
    Changing to innerText actually works and makes sense to me :) – ABOS Jan 22 '19 at 20:38
  • 1
    @DanielBeck I added a working example with `innerText`. – mahalde Jan 22 '19 at 20:39
  • although this works, it is a bad practice IMHO. We should update model and let angular handle the rest. hacks like innerText does not apply to general cases where we want html, and it is confusing – ABOS Jan 22 '19 at 20:42
  • It doesn't work. Your example fails the same way as described in the question: the direct DOM manipulation causes later changes to the `content` variable to not appear in the DOM (your "modified after timeout" never shows up if you change the innerText before the timeout completes.) – Daniel Beck Jan 22 '19 at 20:43
  • I agree, doing it with innerText feels really hacky and doesn't work well. @DanielBeck the stackblitz demo seems to work on my end. – mahalde Jan 22 '19 at 20:48
  • Following the above discussion, I have created a simple demo https://stackblitz.com/edit/angular-vbxpim where we simply pass object around and update it. – ABOS Jan 22 '19 at 20:49
  • @Flix, I have upvoted your answer since I think it helps us understand why angular `"fails"` to work in this case. – ABOS Jan 22 '19 at 20:51
  • Please don't upvote incorrect answers. The code in this answer is functionally identical to the code in the question; upvoting it gives the false impression to future users that making this change will fix the problem, which is not the case. (Depending on comments to point out that the answer is incorrect isn't safe; comments on this site get deleted and changed and moved around much more readily than the actual answer text.) – Daniel Beck Jan 22 '19 at 20:54
  • 1
    @DanielBeck, sorry, I don't follow you. why do you say it is not working? – ABOS Jan 22 '19 at 21:00
  • 1
    Wow, ok, I stand partially corrected -- I just checked other browsers and it does work in Chrome (only). Firefox and Safari both fail (they get stuck on 'modified by directive' even after the timeout fires.) I'm genuinely surprised by that result, and apologize for my earlier vehemence. (But I think we're all in agreement that this is bad practice, still? Even if it works in one browser?) – Daniel Beck Jan 22 '19 at 21:00
  • 1
    @DanielBeck I absolutely agree that it is bad practive, and am also surprised that it works only in Chrome. – mahalde Jan 22 '19 at 21:07
  • 1
    Cheers, I'll stand down :) (you may want to mention in the answer that the results here seem to be browser-specific, in case the comment thread gets lost.) – Daniel Beck Jan 22 '19 at 21:11