How does DOMContentLoaded relate to Web Components?

It's age-old common sense to start manipulating the DOM only once it's ready and we can be certain all the elements are available, and in the post-jQuery days we're all using the DOMContentLoaded event for this.

Now web components (especially in the form of autonomous custom elements) tend to create their own HTML, usually in the connectedCallback() lifecycle method.

1st question:

How does DOMContentLoaded relate to (autonomous) custom elements? Will the event occur only after all component connectedCallbacks have finished? If not, how can I make sure certain code only executes after the web components are done initializing?

2nd question, totally related:

How do web components relate to the defer attribute of the script element?

Answers:

Answer

I'm not into web-components, but I would say... not at all.

Your component is being defined by your script, but before it is, the browser will still parse the markup and execute all the synchronous scripts as usual, and fire the DOMContentLoaded when it's done.

So if you do define your CustomElements synchronously before the DOMContentLoaded event fired, then your elements connectedCallback will have fired (because it's not an Event, it's a callback, and is called synchronously).

if (window.customElements) {

  addEventListener('DOMContentLoaded', e => console.log('DOM loaded'));

  class MyCustom extends HTMLElement {
    connectedCallback() {
      console.log('Custom element added to page.');
    }
  }

  customElements.define('my-custom', MyCustom);
  console.log('Just defined my custom element')

} else {
  console.log("your browser doesn't have native support");
}
<my-custom></my-custom>

But if you do wait for the DOMContentLoaded event, then... the callbacks will fire after.

if (window.customElements) {

  addEventListener('DOMContentLoaded', e => console.log('DOM loaded'));

  class MyCustom extends HTMLElement {
    connectedCallback() {
      console.log('Custom element added to page.');
    }
  }

  setTimeout(()=> customElements.define('my-custom', MyCustom), 2000);

} else {
  console.log("your browser doesn't have native support");
}
<my-custom></my-custom>

But in no way is DOMContentLoaded waiting for anything else than the end of the synchronous execution of all the scripts, just like if you didn't had any CustomElement in there.


As to your last question, as said in the docs about the defer attribute, the scripts with such an attribute will get parsed before the DOMContentLoaded fires.

Answer

They are on different axes. DOMContentLoaded is about parsing the initial HTML document, so the raw "file" which is downloaded. Components are ready when they are defined.
I am not familiar with the topic either, so just modified the MDN example to completely decouple the two happenings via a button press. I assumed definition of new components can happen any time, and indeed that is the case.
MDN example is on GitHub, linked from CustomElementRegistry.define (and also from various other pages). They have a live variant too on GitHub IO, but of course that is just the original example.
Putting the complete example here feels hopeless, so this is just the modified HTML:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Pop-up info box — web components</title>
  </head>
  <body id="body">
    <h1>Pop-up info widget - web components</h1>

    <form>
      <div>
        <label for="cvc">Enter your CVC <popup-info img="img/alt.png" text="Your card validation code (CVC) is an extra security feature — it is the last 3 or 4 numbers on the back of your card."></label>
        <input type="text" id="cvc">
      </div>
    </form>

    <!--<script src="main.js"></script>-->
    <script>
      function test(){
        var x=document.createElement("script");
        x.src="main.js";
        document.getElementById("body").appendChild(x);
      }
      customElements.whenDefined("popup-info").then(function(){alert("popup-info");});
      document.addEventListener("DOMContentLoaded",function(){alert("DOMContentLoaded")});
    </script>
    <button onclick="test()">Test</button>
  </body>
</html>

So main.js is not loaded automatically, only after pressing the button, and it still works. "DOMContentLoaded" popup has happened by a long time then. However, there is a CustomElementRegistry.whenDefined() which is patient, and fires only after the custom element gets defined. I think this is what you could use, perhaps subscribing to it from DOMContentLoaded, so your final event would be guaranteed to happen when both the DOM and the custom element is ready. Downside is that you have to know the name of the custom element(s) you are waiting for. (Untested assumption, but based on the scheduling diagrams on https://html.spec.whatwg.org/multipage/scripting.html, one could probably make whenDefined() to happen before DOMContentLoaded. By the way: the diagrams also show what defer does, so if you put whenDefined() into a deferred script, the callback will happen after DOMContentLoaded, or at least that is what I believe)

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.