Skip to main content

Sharing state between elements

Just a quick pointer I often notice people don’t know about on the Polymer slack group (or know about it but don’t understand it): It’s possible to share information between all elements of a certain type. This can be both great for performance (retrieving information only once instead of once for every element) and usability (once any <auth-api> element in my application finishes authentication all the other elements on the page can use the same token).

Note: This post is going to be a bit more verbose and NOT to the point, just to cover some extra ground which might seem irrelevant to most of you O:) .

What are HTML imports

Let’s start with HTML imports, Eric Bidelman introduces them in one of his blogposts as the magic solution that’s supposed to make embedding HTML easy. The reason I am bringing them up is that understanding what they are and aren’t for and what they exactly do will make everything a whole lot easier to comprehend (and I just want to acknowledge it’s confusing).

So what actually happens when you include

<link rel="import" href="myfile.html">

in your file? The answer is a bit vague (and I never read through the spec word for word), but it boils down to:

  1. It will not include or import the entire myfile.html in that specific document.
  2. It will specifically include/import <script> tags (as if they were in the parent) and other <link> tags, but only once (!!!).
  3. Other stuff inside myfile.html file can be accessed through Javascript through the linkElement.import interface.

What the ‘only once’ means is that if you have a myfile.html containing

<script>
 alert('once?');
</script>

and next include

<link rel="import" href="myfile.html">
<link rel="import" href="myfile.html">

in your header it will still only alert once.

Variable scoping in Javascript

The best thing you can do at this point is just grab a good book covering the basics of Javascript and read it carefully. Realistically however I know most people haven’t, so let me just do a very quick check up:

var one = function(obj){
  //local scope
  var a = 0;
  obj.two();
};

//global scope
var a = 1;
var b = 2;
one({
  //object property
  c: 3,
  two: function(){  
    //local scope
    var b = 4;
    var c = 5;
    alert(a); // 1, from the global scope, the `a=0` is totally irrelevant as it's not in the chain
    alert(b); // 4, from the local scope (as local trumps global)
    alert(this.c); // 3, from the object properties
  }
});

Take a good at the above whether it makes all sense. I have been thinking about the best way to explain this if it doesn’t and I was hoping the comments would help. If not I would recommend reading this answer and playing around with all the scopes till they all start making sense.

Executing code only once per polymer element registration

So if we have the following Polymer element .html:

<script>
  alert('Message 1');
</script>
<dom-module id="...">
  <template>
  </template>
  <script>
    alert('Message 2');

    Polymer({...});
  </script>
</dom-module>

This means that no matter how often it gets <link rel='import'>ed, Message 1 will obviously only trigger once. Similarly the dom-module only gets parsed once and somewhat logically the Polymer team wants every element to only register once, so the <script> in a <dom-module> gets triggered only once as well. Combine this with the knowledge about variable scoping in Javascript and you will (hopefully) realize that you can use the location where Message 2 is to have variables that can be accessed from within the Polymer element and all of which will point to the same values. For lack of a better name lets call it the element-type scope.

Just a quick example of this:

<dom-module id="my-counter">
  <template>
  </template>
  <script>
    var i = 0;        
    Polymer({
      ready: function(){
        i++;
        alert(i);
      }
    });
  </script>
</dom-module>

So how can we make use of this

Let’s say we have a <synced-setting> element which retrieves elements through an API and synces them with the offline localStorage. We can now do the sync only once and load the settings in to the shared space

Let’s go through the code snippet by snippet. We define the element:

<dom-module id="synced-settings">
  <script>

We define a shared variable whether any element started a sync

    var syncStarted = false;

And we set up a deferred promise construction which will allow us to wait for the sync to finish (if this looks weird read up on Promises).

    var settingResolution;
    var settings = new Promise(function(resolve){
      settingResolution = function(settings){
        resolve(settings);
      };
    });

We register our element

    Polymer({
      ready: function(){

If syncStarted is still false it means none of the elements have got its ready state fired.

        if(!syncStarted){
          syncStarted = true;
          this.sync();
        }
      },

      sync: function(){

We call our API function to retrieve the settings

        this._myCustomRetrieveSettingAsync(function(settings){

We sync them up and then resolve our element-type scoped settings.

          [...] //do sync
          settingResolution(settings);
        });
      },

And we can have a getSetting function that will work on any of the elements no matter when the settings get loaded.

      getSetting: function(setting){
        return new Promise(function(resolve){
          settings.then(function(settings){
            resolve(settings[setting]);
          });
        });
      }

And that’s it.

    });
  </script>
</dom-module>

Conclusion

Considering how messy this blog post ended up I am not surprised people are confused by this. An alternative way to share state is using iron-meta, but in my humble opinion that element is a bit immature (data bindings never seem to work magically with it the way they should) and adds a lot of complexity compared to the above. Of courseiron-meta still is a great solution if you need to share information between two different elements, but once you intuitively understand the above I think it’s by far the better solution.

PS. My apologies if this post is totally unclear. I just wanted to have something to point to when people ask about mono-state portions of elements and/or need this to continue with their projects.

Comments

Popular posts from this blog

Components which refuse to live in the Shadows

I still owe you guys two more posts about Letsencrypt and Service Workers, but let me just share a short article about my struggle getting Recaptcha working in the Shadow DOM… and a crazy solution how to get such components working none the less. You can’t use the standard <div class="g-recaptcha"></div> , because Googles script simply won’t see it. You can’t use the standard Javascript API rendering mechanism, because first of all most of the documentation uses it with an id reference (which isn’t going to be found inside the Shadow DOM), but even if you use a direct element reference there (which will actually show the Recaptcha box) we reach the most painful issue: Recaptcha stays stuck on the spinner after clicking the checkbox . And yes, I checked, it’s really the Shadow DOM causing the issue, likely due to some crazy callback to some global or something. So how do we get it to work? By putting it in to the light DOM… which is far easier said than done...

Lazy loading in the HTTP/2 world

Already touched upon this topic in my HTTP/2 write-up (don’t do this without setting up HTTP/2 first), but although I did initial testing back then only got back to it seriously yesterday. So, let met give some very quick instructions on how to get it working in PSK, though likely it will be similar in any other Polymer application. <page-*> elements Start of with making every page an element. In my case these elements are called <page-*> elements, so your index.html looks something like: <iron-pages attr-for-selected="data-route" selected="{{route}}"> <page-welcome data-route="home"></page-welcome> [...] </iron-pages> It’s possible to set this up in different ways as well, but this is the best way I have found so far to organize my application in Polymer. Lazy loading them My approach is to trigger the loading of the elements from the router. This might be somewhat debatable from a logical poi...

Do JS libraries belong in the DOM?

UI-less custom elements? So, when you start using Polymer you will at some point encounter <iron-ajax> and if you’re like me you will be somewhat confused by the fact that it’s an element. After all, it has no UI and and seems to be entirely misplaced in the DOM. Now, after that you can do one of two things: Either accept it as ‘the way it works in Polymer land’ or you can just ignore it and not use elements like that. I - somewhat uncomfortably - did the former 1 and continued making a lot of elements like that. I made complex <auth-api> elements that would consume my API and toolbox elements like <text-utils> that provided a set of functions to work with text. And all seemed good in the land of Polymer. Till on a fateful day I was implementing a complex web worker and I realized I couldn’t use any of those libraries as web workers (and service workers) do not implement the DOM. Which sucked big time as I need functions on my <text-utils> element. Now,...