How To Append Dynamic HTML With Scripts to the DOM
A Typical Use Case
Let’s make an example: our frontend code makes a GET request to the server to read a dynamic piece of HTML that have to be included in our page.
At this point we would usually append that piece of HTML with some code like:
// ... somewhere in our code we get the reference to the container element...
const containerElement = document.getElementById('container');
const html = '<div>Hello from server</div>';
// Append the content of "html" variable to the container element
containerElement.appendChild(containerElement);
Or also:
// ... somewhere in our code we get the reference to the container element...
const containerElement = document.getElementById('container');
const html = '<div>Hello from server</div>';
// Make the content of "html" variable the content of the container element
containerElement.innerHTML = html;
Special Use Case
The above one was a simple example. But what if the response from the server was something like the following?
<div>
<button onclick="showAlert()">CLICK ME</button>
</div>
<script type="text/javascript">
function showAlert() {
alert('Hello there!');
}
</script>
You’re starting to get where I’m heading, right?
That <script>
tag will actually be included in the DOM, but its code won’t be executed.
That’s because we’re appending the HTML we receive from the server in a way that it’s not attached to the active context of the page.
Fortunately there’s a way to append HTML allowing the execution of scripts inside of it.
The Solution: The Range.createContextualFragment()
Method
The solution I’m going to show you involves the use of the Range.createContextualFragment()
method and it’s simple as the following few lines of code:
// ... somewhere in our code we get the reference to the container element...
const containerElement = document.getElementById('container');
const range = document.createRange();
const contextualFragment = range.createContextualFragment(containerElement);
containerElement.appendChild(contextualFragment);
With this code, any scripts included in the HTML contained in the html
variable will be executed once appended to the DOM.
Beware of Security Risks
The above solution can be useful in some (usually rare) cases, but be aware that this approach comes with its risks: you should use it only when you totally trust the source of the HTML that you receive and if you’re 100% sure that this will not contain something that could represent a security breach or some unwanted content or behavior in your page.
A Real World Example
The technique that I’ve just described is the one that I’m actually using in the WidgetAreaDirective
directive inside the NgWP Theme Kit library.
The directive calls a custom API endpoint (located in the theme’s files in WordPress) and retrieves the HTML of the specified widget area ID.
When the server answers back with the HTML content of the widget area, we append it to the DOM using the same technique described in this article:
private _updateWidgetHtml(html: string) {
const container = this._elementRef.nativeElement as HTMLElement;
/**
* Creating a "contextual fragment" will allow the execution
* of any scripts included in the received HTML.
*/
const range = document.createRange();
const fragment = range.createContextualFragment(html);
container.appendChild(fragment);
}