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:
- It will not include or import the entire
myfile.html
in that specific document. - It will specifically include/import
<script>
tags (as if they were in the parent) and other<link>
tags, but only once (!!!). - Other stuff inside
myfile.html
file can be accessed through Javascript through thelinkElement.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 Promise
s).
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
Post a Comment