omega omega - 1 month ago 11
HTML Question

Getting null when trying to select a template element in an html import

In my app, I do an html import from

A
to a file
B
which has this. But it alerts null. If I open
B
directly in the browser, it alerts the template HTML dom element. How can this happen, which this same code pretty much is from google own documents for web components https://developers.google.com/web/fundamentals/architecture/building-components/customelements.

<template id="x-foo-from-template">

</template>

<script>
alert(document.querySelector('template'));
</script>


This is googles example:

<template id="x-foo-from-template">
<style>
p { color: orange; }
</style>
<p>I'm in Shadow DOM. My markup was stamped from a &lt;template&gt;.</p>
</template>

<script>
customElements.define('x-foo-from-template', class extends HTMLElement {
constructor() {
super(); // always call super() first in the ctor.
let shadowRoot = this.attachShadow({mode: 'open'});
const t = document.querySelector('#x-foo-from-template');
const instance = t.content.cloneNode(true);
shadowRoot.appendChild(instance);
}
...
});
</script>


Thanks

Answer Source

Why does this Happen?

Two factors to take into consideration when importing a file that contains a script, and a template:

  1. The script will execute at import time, while markup and other resources need to be added to the main page explicitly
    • As pointed out in this article on imports (by same author as Google docs, linked above):

An import link doesn't mean "#include the content here". It means "parser, go off an fetch this document so I can use it later". While scripts execute at import time, stylesheets, markup, and other resources need to be added to the main page explicitly.

  1. A script in an import is executed in the context of the window that contains the imported document. So window.document refers to the main page document, not the template document.

This should explain why your script alerts null.

In response to the question in the comment below: How does Google's example above differ from the example in question?

In Google's example, the call to document.querySelector() is in the constructor function of the custom element, which is called when the element is instantiated. Therefore, whether or not the example uses a file import to define the custom element, that code will not be executed until the element is instantiated.

How to make it work:

You can create a reference to the import document itself where the template can be found.

// importDoc references this import's document
var importDoc = document.currentScript.ownerDocument;

alert(importDoc.querySelector('template'));

Or, you can obviously query the document after you've inserted the contents of the template into the document. So this should also work:

var link = document.querySelector('link[rel="import"]');
var content = link.import;
var el = content.querySelector('template');

// Append template to document
document.body.appendChild(el.cloneNode(true));

// Now you can query the main the document
alert(document.querySelector('template'));