Polymer 1.0 Global Variables

In Polymer 0.5 the advice on globals was as outlined in this question/answer:

Polymer global variables

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?

Answers:

Answer

Polymer element <iron-meta> is also an option. For me this was the easiest solution.

Answer

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:

  1. I have posted a full example as a Gist on Github
  2. 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>

Answer

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.

Answer

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.

Answer

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

Answer

I've combined all suggestions above into the following global polymer object

<dom-module id="app-data">
</dom-module>
<script>
    (function () {
        var instances = [];
        var vars = Object.create(Polymer.Base);
        var commondata = {
            get loader() {
                return vars.get("loader");
            },
            set loader(v) {
                return setGlob("loader", v);
            }
        };

        function setGlob(path, v) {
            if (vars.get(path) != v) {
                vars.set(path, v);
                for (var i = 0; i < instances.length; i++) {
                    instances[i].notifyPath("data." + path, v);
                }
            }
            return v;
        }

        Polymer({
            is: 'app-data',
            properties: {
                data: {
                    type: Object,
                    value: commondata,
                    notify: true,
                    readonly: true
                }
            },
            created: function () {
                instances.push(this);
            },

            detached: function () {
                var i = instances.indexOf(this);
                if (i >= 0) {
                    instances.splice(i, 1);
                }
            }
        });
    })();
</script>

and use it elsewere like

<dom-module id="app-navigation">
    <style>

    </style>
    <template>
        <app-data id="data01" data="{{data1}}" ></app-data>
        <app-data id="data02" data="{{data2}}"></app-data>
        <span>{{data1.loader}}</span>
        <span>{{data2.loader}}</span>
    </template>

</dom-module>
<script>

    (function () {
        Polymer({
            is: 'app-navigation',
            properties: {
            },
            ready: function () {
                this.data1.loader=51;
            }
        });
    })();
</script>

Changing either data1.loader or data2.loader affects other instances. You should to extend commondata object to add more global properties like it shown with loader property.

Answer

It is much easier to achieve the same effect of global variables if you wrapped your application in a template. Watch the explanation in this video (I linked to the exact minute and second where the concept is explained).

Answer

Using ootwch's 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.

In case anyone else runs into the same problem, I think I fixed it by adding a ready callback like this:

ready: function() {
  const key = this.getAttribute('key')
  if (!key) {
    throw new Error('cm-app-global element requires key')
  }

  const val = vars.get(key)
  if (!!val) {
    this.set('data', val)
  }
},

Hope this saves someone some pain.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.