javascripthtmlfrontendweb-component

when can we access the children elements of a custom component using javascript?


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


Solution

  • 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.