Polymer 1.0 Global Variables
Solution 1
Polymer element <iron-meta>
is also an option. For me this was the easiest solution.
Solution 2
I've extended Etherealones' solution to work as a Behavior, and to extend Polymers "set" and "notifyPath" methods to trigger the updates automatically. This is as close as i could get to a true databinding across components/elements:
globals-behavior.html:
<script>
var instances = [];
var dataGlobal = {};
var GlobalsBehaviour = {
properties: {
globals: {
type: Object,
notify: true,
value: dataGlobal
}
},
ready: function() {
var _setOrig = this.set;
var _notifyPathOrig = this.notifyPath;
this.set = function() {
_setOrig.apply(this, arguments);
if (arguments[0].split(".")[0] === "globals") {
this.invokeInstances(_notifyPathOrig, arguments);
}
};
this.notifyPath = function(path, value) {
_notifyPathOrig.apply(this, arguments);
if (arguments[0].split(".")[0] === "globals") {
this.invokeInstances(_notifyPathOrig, arguments);
}
};
},
invokeInstances: function(fn, args) {
var i;
for (i = 0; i < instances.length; i++) {
instance = instances[i];
if (instance !== this) {
fn.apply(instance, args);
}
}
},
attached: function() {
instances.push(this);
},
detached: function() {
var i;
i = instances.indexOf(this);
if (i >= 0) {
instances.splice(i, 1);
}
}
};
</script>
And in all polymer elements that should have access to the globals variable:
<script>
Polymer({
is: 'globals-enabled-element',
behaviors: [GlobalsBehaviour]
});
</script>
Examples:
- I have posted a full example as a Gist on Github
- Here's a snippet to see it in action:
<!DOCTYPE html>
<html>
<head>
<title>Globals Behavior Example</title>
<link rel="import" href="//rawgit.com/Polymer/polymer/master/polymer.html">
<dom-module id="globals-enabled-element">
<template>
<input type="text" value="{{globals.my_variable::input}}">
</template>
<script>
var instances = [];
var dataGlobal = {};
var GlobalsBehaviour = {
properties: {
globals: {
type: Object,
notify: true,
value: dataGlobal
}
},
ready: function() {
var _setOrig = this.set;
var _notifyPathOrig = this.notifyPath;
this.set = function() {
_setOrig.apply(this, arguments);
if (arguments[0].split(".")[0] === "globals") {
this.invokeInstances(_notifyPathOrig, arguments);
}
};
this.notifyPath = function(path, value) {
_notifyPathOrig.apply(this, arguments);
if (arguments[0].split(".")[0] === "globals") {
this.invokeInstances(_notifyPathOrig, arguments);
}
};
},
invokeInstances: function(fn, args) {
var i;
for (i = 0; i < instances.length; i++) {
instance = instances[i];
if (instance !== this) {
fn.apply(instance, args);
}
}
},
attached: function() {
instances.push(this);
},
detached: function() {
var i;
i = instances.indexOf(this);
if (i >= 0) {
instances.splice(i, 1);
}
}
};
</script>
<script>
Polymer({
is: 'globals-enabled-element',
behaviors: [GlobalsBehaviour]
});
</script>
</dom-module>
</head>
<body>
<template is="dom-bind">
<p>This is our first polymer element:</p>
<globals-enabled-element id="element1"></globals-enabled-element>
<p>And this is another one:</p>
<globals-enabled-element id="element2"></globals-enabled-element>
</template>
</body>
</html>
Solution 3
I have tried to improve on Alexei Volkov's answer, but I wanted to define the global variables separately. Instead of the getters/setters I used the observer
property and saved the key together with the instances.
The usage is:
<app-data key="fName" data="{{firstName}}" ></app-data>
whereas the key
property defines the name of the global variable.
So for example you can use:
<!-- Output element -->
<dom-module id="output-element" >
<template>
<app-data key="fName" data="{{data1}}" ></app-data>
<app-data key="lName" data="{{data2}}" ></app-data>
<h4>Output-Element</h4>
<div>First Name: <span>{{data1}}</span></div>
<div>Last Name: <span>{{data2}}</span></div>
</template>
</dom-module>
<script>Polymer({is:'output-element'});</script>
Definition of the <app-data>
dom module:
<dom-module id="app-data"></dom-module>
<script>
(function () {
var instances = [];
var vars = Object.create(Polymer.Base);
Polymer({
is: 'app-data',
properties: {
data: {
type: Object,
value: '',
notify: true,
readonly: false,
observer: '_data_changed'
},
key: String
},
created: function () {
key = this.getAttribute('key');
if (!key){
console.log(this);
throw('app-data element requires key');
}
instances.push({key:key, instance:this});
},
detached: function () {
key = this.getAttribute('key');
var i = instances.indexOf({key:key, instance:this});
if (i >= 0) {
instances.splice(i, 1);
}
},
_data_changed: function (newvalue, oldvalue) {
key = this.getAttribute('key');
if (!key){
throw('_data_changed: app-data element requires key');
}
vars.set(key, newvalue);
// notify the instances with the correct key
for (var i = 0; i < instances.length; i++)
{
if(instances[i].key == key)
{
instances[i].instance.notifyPath('data', newvalue);
}
}
}
});
})();
</script>
Fully working demo is here: http://jsbin.com/femaceyusa/1/edit?html,output
Solution 4
Sjmiles, one of Polymer's creators just posted the following snippet to the Polymer slack room as an example of shared data:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="shared-data element and repeats">
<base href="http://milestech.net/components/">
<script href="webcomponentsjs/webcomponents-lite.min.js"></script>
<link href="polymer/polymer.html" rel="import">
</head>
<body>
<demo-test></demo-test>
<script>
(function() {
var private_data = [{name: 'a'}, {name: 'b'}, {name: 'c'}];
Polymer({
is: 'private-shared-data',
properties: {
data: {
type: Object,
notify: true,
value: function() {
return private_data;
}
}
}
});
})();
Polymer({
is: 'xh-api-device',
properties: {
data: {
type: Array,
notify: true
},
_share: {
value: document.createElement('private-shared-data')
}
},
observers: [
'dataChanged(data.*)'
],
ready: function() {
this.data = this._share.data;
this.listen(this._share, 'data-changed', 'sharedDataChanged');
},
dataChanged: function(info) {
this._share.fire('data-changed', info, {bubbles: false});
},
sharedDataChanged: function(e) {
this.fire(e.type, e.detail);
},
add: function(name) {
this.push('data', {name: name});
}
});
</script>
<dom-module id="demo-test">
<template>
<h2>One</h2>
<xh-api-device id="devices" data="{{data}}"></xh-api-device>
<template is="dom-repeat" items="{{data}}">
<div>name: <span>{{item.name}}</span></div>
</template>
<h2>Two</h2>
<xh-api-device data="{{data2}}"></xh-api-device>
<template is="dom-repeat" items="{{data2}}">
<div>name: <span>{{item.name}}</span></div>
</template>
<br>
<br>
<button on-click="populate">Populate</button>
</template>
<script>
Polymer({
populate: function() {
this.$.devices.add((Math.random()*100).toFixed(2));
// this works too
//this.push('data', {name: (Math.random()*100).toFixed(2)});
}
});
</script>
</dom-module>
</body>
</html>
I've actually moved my app to using simple data binding, so I'm not sure of the validity of this approach, but maybe it would be useful to someone.
Solution 5
I have implemented a pattern like iron-signals
uses for this purpose. So the basic principle is that you manually notify other instances when an update occurs.
Consider this:
<dom-module id="x-global">
<script>
(function() {
var instances = [];
var dataGlobal = {};
Polymer({
is: 'x-global',
properties: {
data: {
type: Object,
value: dataGlobal,
},
},
attached: function() {
instances.push(this);
},
detached: function() {
var i = instances.indexOf(this);
if (i >= 0) {
instances.splice(i, 1);
}
},
set_: function(path, value) {
this.set(path, value);
instances.forEach(function(instance) {
if (instance !== this) { // if it is not this one
instance.notifyPath(path, value);
}
}.bind(this));
},
notifyPath_: function(path, value) {
instances.forEach(function(instance) {
instance.notifyPath(path, value);
});
},
fire_: function(name, d) {
instances.forEach(function(instance) {
instance.fire(name, d);
});
},
});
})();
</script>
</dom-module>
You will simple call the version that have an underscore suffix like fire_
when you are firing an event. You can even create a Polymer Behavior of some sort with this pattern I guess.
Beware that preceding underscore properties are already used by Polymer so don't go ahead and convert these to _fire
.
P.S.:
I didn't look around to solve how to reflect the notification of this.push(array, value);
as I don't need it. I don't know if it's possible this way. Should go find Polymer.Base.push
.
Grokys
Updated on June 02, 2022Comments
-
Grokys almost 2 years
In Polymer 0.5 the advice on globals was as outlined in this question/answer:
However in Polymer 1.0 this doesn't seem to work. Change notifications are not automatically generated on the underlying model, they are generated on the
<dom-module>
instead which means that change notifications will be generated on only one of the<app-globals>
.What is the recommended way of implementing this pattern in Polymer 1.0?
-
zerodevx almost 9 yearsThanks for posting the example! At first I thought it might be an anti-pattern to use 0.5-like global vars (since no mention of this was made in 1.0 docs), so I made the rather painful change to pure data-binding in my app. Glad that there's a way to achieve global var functionality in 1.0 too.
-
Tobias Strebitzer almost 9 yearsGreat approach! I've extended it to be used as a Behavior, will post here as an answer.
-
Etherealone almost 9 yearsI think this looks a bit overly complicated. So I guess it also replicates the array-changed event fired by
this.push
. -
Etherealone almost 9 yearsThis is nice. Maybe find a way for
push
,pop
,shift
,unshift
, andsplice
too? Polymer.push prototype may help maybe. There is an internal_notifySplice
apparently. No idea how stable is that interface though. -
Etherealone almost 9 yearsAlso, I'd rather provide the global variable to element myself, maybe you should expect the variable name
_globals
and useconsole.warn
inBehavior.ready
if it does not exist. A single global global does not sound good. -
Let Me Tink About It almost 9 yearsYour link did not show up on the post. Please repost with the link.
-
Grokys over 8 yearsI agree - implementing it as a behavior suggests that it's ready to be used by different components, but as far as I can tell, that is not the case here.
-
ootwch over 8 yearsNot as it is currently built, as the Polymer observer does not monitor/propagate change events on array elements. Check stackoverflow.com/a/30676440/1878199 for some ideas on how to change this if you really need it. I tend to reduce global variables instead, by adapting the design.
-
jkhoffman about 7 yearsUsing this solution, I ran into a race condition situation with lazy-loaded components. As posted, lazy-loaded components are not initialized with the value from the shared data. See my post for a patch.
-
limitlessloop about 7 yearsCould this be modified to be used so you can import global variables as a dependency for a given custom element?
-
limitlessloop about 7 yearsMade a bit more sense to me with regards to my basic need of sharing variables from one source to other elements. I'm guessing you could change
data
from an array to an object if you needed to? -
limitlessloop about 7 yearsI couldn't get this to work with Polymer 2.0, any idea what you'd have to update for it to work?
-
Ben Davis over 6 yearsOut of curiosity, why are you using
this.getAttribute('key')
instead of justthis.key
?