So I'm trying to build a custom component using vanilla javascript, which will do certain things depending on the number of children it has, meaning it has to count said children
If I have the following markup (where the custom component is called "my-component")
<my-component>
<div></div>
<!-- ...arbitrary number of child elements -->
</my-component>
And the following javascript code in the <head></head>
to ensure it's loaded before the <body></body>
is parsed
class MyComponent extends HTMLElement {
constructor(){
super()
this.children.length
//do stuff depending on the number of children
}
//or
connectedCallback () {
this.children.length
//do stuff depending on the numbre of children
}
}
customElements.define("my-component",MyComponent)
this.children.length
will return 0 in both cases, despite the elements showing on the screen afterwards, and being able to inspect the custom element on the console and get the expected number of children with Element.children.length
. I suppose that this means the children elements are not yet available at the time the constructor
nor the connectedCallback
are run.
Is there any way to specify in my element's class definition a function that will trigger when the children elements become available, so that I can do stuff with them? I was hoping for a "childElementsReady" callback or something similar, but I guess that it doesn't exist. I don't know if there's a really obvious way to deal with this that I'm just missing, because this seems like something that I should be able to do relatively easily
A MutationObserver is the best way to handle this. You can set one up in connectedCallback
to observe changes to the Light DOM - in this case it's enough to observe childList
only:
class MyElement extends HTMLElement {
constructor() {
super();
this.onMutation = this.onMutation.bind(this);
}
connectedCallback() {
// Set up observer
this.observer = new MutationObserver(this.onMutation);
// Watch the Light DOM for child node changes
this.observer.observe(this, {
childList: true
});
}
disconnectedCallback() {
// remove observer if element is no longer connected to DOM
this.observer.disconnect();
}
onMutation(mutations) {
const added = [];
// A `mutation` is passed for each new node
for (const mutation of mutations) {
// Could test for `mutation.type` here, but since we only have
// set up one observer type it will always be `childList`
added.push(...mutation.addedNodes);
}
console.log({
// filter out non element nodes (TextNodes etc.)
added: added.filter(el => el.nodeType === Node.ELEMENT_NODE),
});
}
}
customElements.define('my-element', MyElement);
Here onMutation
will be called every time nodes are added to the Light DOM so you can handle any set up here.
Note that, depending on the nodes in the Light DOM, onMutation
can be called more than once when the element is connected to the DOM so it's not possible to say that all the children are 'ready' at any point - instead you must handle each mutation as it comes in.