Monday, October 19, 2015

Web Components lifecycle methods 2:attributeChangedCallback

The Web Component callback attributeChangedCallback seems simple enough. Whenever an attribute of your custoim component is changed, it will be called. The purpose of it is to support dynamic use cases: the web site developer can use standard HTML setAttribute to change attributes for your component, and the callback gives you a chance to react on the changes. Without it, or without an implementation of it, your component will not handle changes. But how does it actually work:

Case 1: modify a custom attribute

The first case is pretty straightforward: we have a custom component that defines some custom attributes. I use my own upper88-wordcloud throughout. It defines a couple of custom attributes, rows and options. When they are updated the callback is called, and I simply rerender the component. I use this in the demo page  with this little code snippet:

window.setInterval(function() {
  var chart = document.getElementById('hello_cloud');
  if (chart) {
   chart.setAttribute('rows', JSON.stringify([
    ['Hello', getRandomValue()],
    ['Word', getRandomValue()],
    ['Cloud', getRandomValue()]
   ], null, 2));
             }
 }, 3000);

As expected this leads to that the component is rerendered with modified data every 3 seconds. Note also that this is a callback: it is called after the attribute has been changed, so you can use the getAttribute method and you will get the new value. Also if you check in the attributes property you will get the new value. So far so good.

Case 2: modify standard attribute

My custom element extends HTMLelement and has of course the standard HTML attributes, like title. It also has the standard support for data-xxx attributes, which will be saved in the dataset property. Are they also covered by the callback??

To try this I added the following to the function above:

    chart.setAttribute('title','New title'+getRandomValue());
    chart.setAttribute('data-xxx','data-yyy'+getRandomValue());

And yes, this also works, and in the same way. The callback is called after the attribute has been updated, so you can use this also in your component.

Case 3: modify standard properties

Now using setAttribute to modify titles works but its a bit complicated. The title is available as a property, and can be modified directly, like this:

chart.title = 'New title'+getRandomValue();

(I use the helper function getRandomValue() to make sure I actually update the value) And yes, this works too. So it seems we can use the callback to monitor changes, it covers changes not made with setAttribute also.

Conclusion

The attributeChangedCallback helps you develop WebComponents that support dynamic changes.  Developers using your component can dynamically modify component instan ces using standard javascript techniques like setAttribute. You can use the callback to monitor changes not only in your custom attributesbut also in standard HTML attributes.

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