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?
The DOMContentLoaded event fires when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading. A different event, load , should be used only to detect a fully-loaded page.
The load event is fired when the whole page has loaded, including all dependent resources such as stylesheets and images. This is in contrast to DOMContentLoaded , which is fired as soon as the page DOM has been loaded, without waiting for resources to finish loading.
DOMContentLoaded indicates when the browser has finished parsing the document (but other resources such as images and stylesheets may/may not have been downloaded). It is represented as a blue line. The load event fired will occur once all the initial resources (images, stylesheets, JavaScript) have been downloaded.
If you want to know "exactly" if DOMContentLoaded was fired, you could use a boolean flag like this: var loaded=false; document. addEventListener('DOMContentLoaded',function(){ loaded=true; ... }
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)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With