Search code examples
javascriptweb-componentcustom-element

Why do I have to defer customElement definitions?


It seems that I have to define a custom element only after the HTML body has been parsed. If I define it before, the contents of the custom element are empty.

Here is an MWE:

<!DOCTYPE html>
<html lang="en">
<head>
  <script>
    customElements.define('test-one',
      class extends HTMLElement {
        constructor() {
          super()
          console.log(this.textContent)
        }
      }
    )
  </script>
</head>

<body>
  <test-one>First.</test-one>
  <test-two>Another.</test-two>

  <script>
    customElements.define('test-two',
      class extends HTMLElement {
        constructor() {
          super()
          console.log(this.textContent)
        }
      }
    )
  </script>
</body>
</html>

test-one outputs "" in the console, test-two outputs "Another.".

However, this seems completely unintuitive and I wasted a lot of time reading the spec, but I found no explanation for this behavior. Any ideas? Where is that specified or documented? And this isn't a Chrome issue, Firefox behaves the same.


Solution

  • Actually you can define a Custom Element before it is added to the DOM.

    What you cannot do is accessing its content (attributes, child tree, properties) in its constructor(), because this elements have not been parsed (as @Patrick Evans suggested).

    In your example you could wait for a little time to access the textContent property.

    constructor() {
      super()
      setTimeout( () => console.log(this.textContent) )
    }
    

    If you still want to put the custom elements definition in the header, you can wait for the page to be loaded.

    window.onload => customElements.define(...)
    

    or, depending on what you are waiting for:

    document.addEventListener( 'DOMContentLoaded', customElements.define(...) )
    

    It's not in black and white in the specs because it's rather a consequence of the parsing proccess, but you can read in this HTML Standard section:

    The element's attributes and children must not be inspected, as in the non-upgrade case none will be present, and relying on upgrades makes the element less usable.

    The "non-upgrade case" is when the element is defined before it is parsed.