Web Component developers do not connect with the connectedCallback (yet)
Tags: webcomponents, javascript, webdev, dom
Published: 2024-01-10 20:30:15

> **Disclaimer:** This blog post suggests using ``setTimeout``

<hr>

This post was originally written as StackOverflow answer in **February 2022**:
[**WTF? ``this.innerHTML`` is an empty string in the ``connectedCallback``**](https://stackoverflow.com/questions/70949141/web-components-accessing-innerhtml-in-connectedcallback/70952159#70952159)

<hr>

# When was DOM parsed?

Key is to understand _**when**_ DOM was parsed

In the script below, everyone will agree the result tab will show:
![](https://i.imgur.com/MaXaekp.png) because the first script  executed _**before**_ the remaining **DOM was parsed**.

{% jsfiddle https://jsfiddle.net/WebComponents/xkur8dvg html,result %}

<img width="100%" style="width:100%" src="https://storage.googleapis.com/ml2grow-intern.appspot.com/blogimg/listen-very-carefully.gif">

### the ``connectedCallback`` fires on the _opening_ tag!

Then how do you think the **DOM Parser** handles this code?

* ``component-1`` is defined **before** DOM is parsed

* ``component-2`` is defined **after** DOM is parsed

_Read the code below, then click the Result Tab_

{% jsfiddle https://jsfiddle.net/WebComponents/fjd0m528 html,result %}

If you **_understand_** why the answer is: 

![](https://i.imgur.com/ZRCy211.png)

You can stop reading here.

<hr>

# ``component-1 connectedCallback()``

Because **the ``connectedCallback`` fires on the _opening_ tag!**  **all following DOM (DIVs in lightDOM) is NOT parsed yet**

That means all ``component-1`` **attributes** (id in above code) are available ON the Web Component, 

But **NOT** its three ``<div>`` child elements IN lightDOM. 

### Wait till lightDOM is parsed

Simplest method to get that lightDOM content is to delay execution till the **Event Loop** is _empty_ again, and you know _**more**_  (and _most likely_ **_all_**) of your lightDOM was parsed. 

With a ``setTimeout``

{% jsfiddle https://jsfiddle.net/WebComponents/n7uqefLs html,result %}

#### Optional background knowledge:  
[Youtube: Jake Archibald on the web browser event loop, setTimeout, requestAnimationFrame](https://www.youtube.com/watch?v=cCOL7MC4Pl0)

### ``component-1`` ``setTimeout`` executes **after DOM is parsed** (the DIVs in lightDOM)

Also note (in the above Results Tab) that ``component-1`` wrote its output _after_ ``component-2``, because of the ``setTimeout`` in ``component-1``

![](https://i.imgur.com/4V7kueA.png)

# BUT!  
> Because the Event Loop can be(come) empty when the (large) DOM is still being parsed!  
>
> This gets you the _next_ **N** elements parsed,  
**not _ALL_** elements in a (**large**) lightDOM!  
>
> Rough tests show around N=1000 (1000 lightDOM elements) are safe to work with.  
but your mileage may vary for complex CPU consuming elements  
Maybe just increase to 1 millisecond setTimeout delay

Sidenote: Should a Web Component with a 1000 Childnodes really be **one** Web Component?

## requestAnimationFrame (rAF)

``requestAnimationFrame`` can also be used. Read!: 
https://stackoverflow.com/questions/71523029/settimeout-vs-requestanimationframe  
But because ``setTimeout`` triggers **later** you know **_more_ N** elements were parsed.

Do watch Jakes video before using rAF!
https://www.youtube.com/watch?v=cCOL7MC4Pl0

### Potential pitfall: the ``attributeChangedCallback``

!!! The ``attributedChangedCallback`` fires **BEFORE** the ``connectedCallback`` for _**every**_ ``attribute`` defined as an observed attribute in ``static get observedAttributes()`` which is declared as **initial** attribute on your Custom Element.

If none of those Observed attributes exist on the Element in the DOM,  ``attributeChangedCallback`` will **not** execute.

## _``setTimeout`` gets you the next N elements_

**N** can be scary for a developer only used to digital 0 and 1 states

If you can't deal with **N**

## get ALL children - parsedCallback()

For getting **all** Child nodes, there is ``parsedCallback()`` by WebReflection.  
But LOC (Lines Of Code) now goes from 1 to 77 :  
 https://github.com/WebReflection/html-parsed-element/blob/master/index.js  
Maybe good to add this to your own BaseClass.
But for small components you are adding more overhead than a ``setTimeout`` or ``rAF`` takes.

### Lifecycle methods in Lit, Stencil, FAST, Hybirds and [61 other tools](https://webcomponents.dev/blog/all-the-ways-to-make-a-web-component/) 

Almost all Tools add their own ``parsedCallback`` like lifecycle methods:  

* https://stenciljs.com/docs/component-lifecycle
* https://lit.dev/docs/components/lifecycle/#custom-element-lifecycle  

Saving _unexperienced_ developers headaches 

Biggest drawback; you learn a Tool, not the Technology.

## What the experts said

Experts discussion has been going on since **2016**

* https://github.com/WICG/webcomponents/issues/551

* https://github.com/WICG/webcomponents/issues/809

That is nearly a **decade** now

The issue is obviously not that big it needs a solution

Not that everyone is aware of the issue...

## Old Mozilla/FireFox bug

Closed bug report: https://bugzilla.mozilla.org/show_bug.cgi?id=1673811

Up until **Spring 2021** there where issues with ``connectedCallback`` in FireFox always firing **late**, so all above mentioned issues never happened in FireFox... but do now.

## Escaping all issues

_9 out 10 devs will not understand **why** they fixed the "bug"_

When Web Components are **defined AFTER DOM was created** you don't have any of these ``connectedCallback`` issues; **_because all DOM was parsed_**

So a ``<script defer src="yourelement.js">`` does the job; **but** will run **after** all DOM is created,  
your components are now created (very) late. So you now have to deal with (more) FOUCs.

![](https://flaviocopes.com/images/javascript-async-defer/with-defer.png)

This also applies to ``<script type="module">`` and other ways of importing modules.  
Your script will _most likely_ be executed **after** DOM was parsed.

Best advice is to just make your Web Components work for the _**before**_ scenario.

### Online IDEs

CodePen, JSFiddle and all those online IDEs **run the JavaScript _AFTER_** the DOM is created!  
So you never experience any issues there.  
Test your code outside of these online IDEs before you take it to production!

# Conclusion

### All together now!

### _the ``connectedCallback`` fires on the opening tag!_

I use ``setTimeout`` in Web Components where I **really** need that ``lightDOM``

Never had an issue, since I started with Web Components in 2017

There are plenty of other workarounds using ``readystatechange``, ``MutationObserver``, ``Promisses`` possible.
Use whatever you think suits you best. It is all about DOM being parsed or not.



<br>
<br>
<hr>
Took me 3 hours to format this blog-post. No need to buy me coffee, or send me money. I am happy enough if you just send some positive karma into this f*ing world.
<br>
<hr>
<br>

{% jsfiddle https://jsfiddle.net/WebComponents/62f1eLay result,html,js %}