Home

Access Web Component Content With A querySelector() Via setTimeout()

Note

This post covers an issue I had with putting a <script> tag in the <head> of my document directly and without type="module". I've since moved the <script> tags to just before the </body> tag and added type="module" which solves the issue for me.

If you do that, you should pretty much be able to ignore this post.

Introduction

I decided to try Web Components for the code blocks on my site1 . I looked at a few component examples2 3 that use querySelector() to access content but I couldn't get the method to work for my code.

The reason was that the examples put the <script> that defines the component after the corresponding custom element was used on the page. I was trying to put the definition in the <head> of the document. The result is that the DOM elements weren't fully loaded when the elements initialized.

The solution I found4 is to wrap the contents of connectedCallback() in a setTimeout() call.

Working Example

Here's an example in action. Check the console and you'll see "Working Node Count: 2" for the two <div> elements in the <aws-wc-working-example> element.

JavaScript

class AwsWcWorkingExample extends HTMLElement {
  connectedCallback() {
    setTimeout(() => {
      const divs = this.querySelectorAll('div');
      console.log(`Working Node Count: ${divs.length}`);
    })
  }
}

customElements.define("aws-wc-working-example", AwsWcWorkingExample);

HTML

<aws-wc-working-example>
  <div>alfa</div>
  <div>bravo</div>
</aws-wc-working-example>

Output

alfa
bravo

The Problem This Fixes

If you don't use setTimeout() the NodeList will be empty. Check the console for this output and you'll see "Problem Node Count: 0" NOTE: This is no longer the case since I moved the script to before the closing body the problem no longer exists

JavaScript

class AwsWcProblemExample extends HTMLElement {
  connectedCallback() {
    const divs = this.querySelectorAll('div');
    console.log(`Problem Node Count: ${divs.length}`);
  }
}

customElements.define("aws-wc-problem-example", AwsWcProblemExample);

HTML

<aws-wc-problem-example>
  <div>charlie</div>
  <div>delta</div>
</aws-wc-problem-example>

Output

charlie
delta

Original Conclusion

I've played with Web Components a few times before and they just kinda didn't make sense to me. It seemed to complicated to do something basic like access the original content inside them. With this new understanding of how to do that, I'm way more excited about them than I have been in the past.

Updated Conclusion

I'm even more excited about Web Components now that I know I don't have to do this hack. It's one of those silly things where I was doing something a little differently that caused an issue, but it wasn't clear to me that what I was doing was weird. Tech is like that sometimes.

~ fin ~

Endnotes

  • I found this solution after banging my head into the desk for a few hours trying to figure out why my code wasn't working. It's solving the problem I had, but I haven't run it enough to know if there are other issues. Based on what I've read, it seems like things should be fine, but I want to through the warning out just in case.

  • According to the Danny Engelman article the setTime() method only works up to a point. For example, if a page has too many DOM elements on it (the article suggest 1000+), the setTimeout() may finish and fire before everything is loaded. The article covers potential fixes for that.

    There are also more details in the article about how/why this works if you want to investigate further.

Footnotes

References