javascripthtmltemplatesweb-componenthtml5-template

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


Solution

  • 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 Eric Bidelman, same author as Google documentation linked in question):

    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. Because the script is executed immediately, while the template hasn't been added to the main page yet.

    How to get the desired result:

    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 query the main document after you insert the template into the document:

    var import = document.querySelector('link[rel="import"]').import;
    var template = import.querySelector('template');
    
    // Append template to main document
    document.head.appendChild(template);
    
    // Now you can query the main the document
    alert(document.querySelector('template'));
    

    How does Google's example differ from the example in question?

    In response to the question in the comment below:

    In Google's example, the call to document.querySelector() is found in the constructor function of the custom element. The constructor function is called when the element is instantiated. Therefore, the element already exists in the main page when this code is run.