Search code examples
htmlweb-componentcustom-elementhtml-templates

How to use Custom Elements with template?


I was trying to understand how web components work so I tried to write a small app that I served on a webserver (tested on Chrome which support rel="import"):

index.html:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <link rel="import" href="my-app.html" />
  </head>
  <body>
    <my-app />
  </body>
</html>

my-app.html:

<template id="template">
  <div>Welcome to my app!</div>
</template>

<script>
class MyApp extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({mode: "open"});
    const template = document.getElementById("template");
    const clone = document.importNode(template.content, true);
    shadow.appendChild(clone);
  }
}
customElements.define("my-app", MyApp);
</script>

But it doesn't seem to work. The <my-app /> tag is not rendered at all in the DOM and I get this error on the console:

Uncaught TypeError: Cannot read property 'content' of null

What cannot I retrieve the template node? What am I doing wrong?

What I would also like to know is if I am allowed to write an HTML document without the boilerplate code (doctype, head, body, ...), because it's meant to describe a component and not an entire document to be used as is. Is it allowed by the HTML5 specs and/or is it correctly interpreted by a majority of browsers?

Thank you for your help.


Solution

  • While inside the template, don't use the document global:

    <template id="template">
      <div>Welcome to my app!</div>
    </template>
    
    <script>
    class MyApp extends HTMLElement {
      constructor() {
        super();
        const shadow = this.attachShadow({mode: "open"});
    
        // while inside the imported HTML, `currentDocument` should be used instead of `document`
        const currentDocument = document.currentScript.ownerDocument;
        // notice the usage of `currentDocument`
        const template = currentDocument.querySelector('#template');
    
        const clone = document.importNode(template.content, true);
        shadow.appendChild(clone);
      }
    }
    customElements.define("my-app", MyApp);
    </script>
    

    Plunker demo: https://plnkr.co/edit/USvbddEDWCSotYrHic7n?p=preview



    PS: Notes com compatibility here, though I assume you know HTML imports are to be deprecated very soon.