Saturday, October 17, 2015

Web Components lifecycle methods 1: createdCallback and attachedCallback

The Web Components standard includes only four lifecycle callback methods:
MethodCalled when
createdCallbackthe element is created
attachedCallbackthe element is inserted into the DOM
detachedCallbackthe element is removed from the DOM
attributeChangedCallback(attrName, oldVal, newVal)an attribute is changed

This is a very simple model and its simplicity has a cost if you try to build your own custom components or use web componets in your site. But lets see how they work.

Create and add elements

The two first methods are called when your page is loaded. Order is pretty obvious: first elements are created, then they are attached to the DOM. Lets investigate how they work in more detail.
There are different ways to create a HTML element, and part of the beauty with Web Components is that you can use all of them: creating your custom HTML element is just like creating a DIV. In this post I will look at three diferent ways to create a custom HTML element:

  1. include in a standard HTML page
  2. create in javascript, with document.createElement
  3. include in a HTML template (in my case in a Polymer iron-page element)

Use web components in a standard HTML page


For this case I have created a small standard web page loaded a Web Component (in this case my own upper88-wordcloud) and created two elements using this custom element. I then open then page in Chrome and set breakpoints in both createdCallback and attachedCallback.
  1. the callbacks are called in this order:
    1. createdCallback element 1
    2. attachedCallback element 1
    3. createdCallback element 2
    4. attachedCallback element 2
  2. already in createdCallback my elements know of their environment, that is parentElement and parentNode are already set, and parentElement has html content. 
  3. HTML initial reflow has not been made, so your element might have no size yet (I have written about this here)

Create web component in javascript

For a more dynamic case creating your element with javascript might be an alternative. Web Components support this also, but it will work slightly different.
To test this I added an empty div to my plain HTML page and a small javascript script:

var wc88 = document.createElement('upper88-wordcloud');
wc88.setAttribute("rows", '[ ["web",10],["components",15],["rocks!",20] ]');
document.getElementById('wc').appendChild(wc88);
  1. callbacks for the javascript element are of course called after the others, with createdCallback first and attachedCallback after
  2. when createdCallback is called parentElement and parentNode are both null, since the browser does not know where the element will end up. In attachedCallback they are set.
  3. HTML reflow has been made so the element actually has a size in the attachedCallback.

Use web components in a HTML template

If you are building a Polymer site it's verey likely that you do not use any of these to methods but instead use your web component in a HTML template, which in turn another web component will actually attach to the document. This is the way for example the Polymer component iron-page works. An example of this is my homepage upper88.com. In that page I use Polymer and iron-pages for page navigation. Note that irob-pages use css display:none to hide the none active sheets, so they will be added to the dom even if the user does not see them (in fact even if the user never navigates to the page). There are three upper88-wordclod elements in this page.
  1. Callback order:
    1. createdCallback element 1
    2. createdCallback element 2
    3. createdCallback element 3
    4. attachedCallback element 1
    5. attachedCallback element 2
    6. attachedCallback element 3
  2. elements know their environment already in createdCallback, parentElement is set
  3. No reflow has been made, so  the element has no size

Conclusion

As you can see the behavour is slightly different in the different scenarios. Even if you can trust the createdCallback to be called first and the attachedCallback to be called after that for each element, you cannot know if createdCallback has been called for all elements before the first attachedCallback or whether the browser will process the elements one by one. And even if parentElement and parentNode might be available in createdCallback (that surprised me) it also might not.
So, some rules:
  • do not make anything in createdCallback that depends on the element environment even if it might work in your page
  • do not count on knowing element size etc in attachCallback, they might not be available yet
  • test your web component in all three scenarios: standard HTML page, javascript creation and HTML template
And still we have only tested this in Chrome.... 

No comments: