Search code examples
javascriptpolymer2-way-object-databinding

attributeChanged not getting fired on 2 way data binding polymer


In my polymer element i have attributeChanged method

Polymer('my-tag', {
  //some code
  attributeChanged: function(attrName, oldVal, newVal) {
    console.log(attrName, 'old: ' + oldVal, 'new:', newVal);
  },
  myAttributeChanged: function(oldVal, newVal){
    console.log("myattribute changed", 'old: ' + oldVal, 'new:', newVal);
  }
});

This get invoked when i manually change attribute.

tag.setAttribute('myAttribute',"wow");

this does not get invoked when i set attribute by 2 way data binding

 <my-tag id="myTagId" myAttribute="{{wowAtrribute}}"></my-tag>
//in script section
this.wowAttribute = "wow";

This does not invoke attributeChanged method, while just invoking myAttributeChanged.

Is this expected behaviour? Is there way to get a blanked Changed method invoked for 2 way data binding?


Solution

  • TLDR: Digging around in poylmer.js and CustomElements.js what you're seeing is indeed how it's supposed to act (may not their intention, but in the code at least).

    In CustomElements.js there's these couple functions which overload together overload the setAttribute and removeAttribute functions to call the changeAttributeCallback function.

      function overrideAttributeApi(prototype) {
        if (prototype.setAttribute._polyfilled) {
          return;
        }
        var setAttribute = prototype.setAttribute;
        prototype.setAttribute = function(name, value) {
          changeAttribute.call(this, name, value, setAttribute);
        };
        var removeAttribute = prototype.removeAttribute;
        prototype.removeAttribute = function(name) {
          changeAttribute.call(this, name, null, removeAttribute);
        };
        prototype.setAttribute._polyfilled = true;
      }
      function changeAttribute(name, value, operation) {
        name = name.toLowerCase();
        var oldValue = this.getAttribute(name);
        operation.apply(this, arguments);
        var newValue = this.getAttribute(name);
        if (this.attributeChangedCallback && newValue !== oldValue) {
          this.attributeChangedCallback(name, oldValue, newValue);
        }
      }
    

    In polymer.js 'attributeChanged' is effectively aliased as 'attributeChanged'. So the only time that callback is used is when you use setAttribute or removeAttribute.

    For individual attributes though it's different. This, in polymer.js is where those 'myAttributeChanged' functions are setup

      inferObservers: function(prototype) {
          // called before prototype.observe is chained to inherited object
          var observe = prototype.observe, property;
          for (var n in prototype) {
            if (n.slice(-7) === 'Changed') {
              property = n.slice(0, -7);
              if (this.canObserveProperty(property)) {
                if (!observe) {
                  observe  = (prototype.observe = {});
                }
                observe[property] = observe[property] || n;
              }
            }
          }
        } 
    

    So basically, for any property that ends in "Changed" polymer sets up an observer for whatever proceeds "Changed". Interestingly, this doesn't actually have to be an attribute defined anywhere in your polymer element to work, but that's a different story.