2

I am currently using Polymer on a project and for some reason I am getting data binding when I do not expect nor want it.

What I currently have is a boilerplate Polymer project that is supposed to have the new element I created (named shokka-eval-chart). This is working great but somehow information is being shared between the three elements.

In order to debug this problem I added in console.log messages that would tell me what was going on. Each time the console logs come back it indicates that the userData has been modified. Where is this modification happening and why?

Some things that you may notice:

  1. User Data is sent in using one way data binding::: [[userInfo.evaluations]]
  2. Index should not be notified of my changes to User Data::: notify: false

Some places that I can think of that I may have an issue:

  1. HTML - I may not be passing things into the tag correctly.
  2. Javascript - I have no idea how but I may be modifying the userData from there.
  3. Polymer - The obvious location to look. I probably am misunderstanding one of the concepts.

Please let me know where this is happening and what I am doing wrong to have this happen. Thanks in advance.

CONSOLE LOGS

shokka-eval-chart.html:89 Jan 16
shokka-eval-chart.html:91 40
shokka-eval-chart.html:92 40
shokka-eval-chart.html:93 0
shokka-eval-chart.html:89 Jan 16
shokka-eval-chart.html:91 0
shokka-eval-chart.html:92 40
shokka-eval-chart.html:93 -40
shokka-eval-chart.html:89 Jan 16
shokka-eval-chart.html:91 -40
shokka-eval-chart.html:92 40
shokka-eval-chart.html:93 -80

INDEX

<body unresolved class="fullbleed layout vertical">
 <span id="browser-sync-binding"></span>
  <template is="dom-bind" id="app">
   <paper-drawer-panel id="paperDrawerPanel">
    <paper-scroll-header-panel main condenses keep-condensed-header>
     <div class="content">
      <iron-pages attr-for-selected="data-route" selected="{{route}}">
       <section data-route="home">
        <paper-material elevation="1">
         <shokka-eval-chart id="comparison1" 
                            user-evaluations="[[userInfo.evaluations]]" 
                            eval-ref="{{baseRef}}/evalResults">
         </shokka-eval-chart>
        </paper-material>
        <paper-material elevation="1">
         <shokka-eval-chart id="comparison2" 
                            user-evaluations="[[userInfo.evaluations]]" 
                            eval-ref="{{baseRef}}/evalResults">
         </shokka-eval-chart>
        </paper-material>
        <paper-material elevation="1">
         <shokka-eval-chart id="comparison3" 
                            user-evaluations="[[userInfo.evaluations]]" 
                            eval-ref="{{baseRef}}/evalResults">
         </shokka-eval-chart>
        </paper-material>
       </section>
      </iron-pages>
     </div>
    </paper-scroll-header-panel>
   ...

Component Code

<link rel="import" href="../../bower_components/polymer/polymer.html">

<dom-module id="shokka-eval-chart">
  <template>
    <style>
      :host {
        display: block;
      }
    </style>
    <google-chart id="{{id}}" 
              type="{{type}}" 
              options='{{options}}'
              data='[[data]]'>
    </google-chart>
  </template>
  <script>
  (function() {
    'use strict';

    Polymer({
      is: 'shokka-eval-chart',

      properties: {
        id: {
          type: String
        },
        userEvaluations: {
          type: String
        },
        evalRef: {
          type: String
        },
        userData: {
          type: Array,
          value: [ ["Date", "Professionalism", "Skills", "Work Completion" ],
                   ["Jan 16", 40, 35, 37 ],
                   ["Feb 16", 39, 35, 34 ],
                   ["Mar 16", 38, 36, 39 ],
                   ["Apr 16", 42, 38, 40 ],
                   ["May 16", 42, 37, 41 ],
                   ["Jun 16", 43, 38, 40 ],
                   ["Jul 16", 44, 40, 41 ],
                   ["Aug 16", 45, 43, 42 ],
                   ["Sep 16", 46, 46, 41 ],
                   ["Oct 16", 45, 44, 42 ] ],
          notify: false
        },
        data: {
          type: Array,
          computed: 'computeData(avgData, userData)',
          notify: false
        },
        options: {
          type: Object,
          value: { "title": "Evaluation Comparison" }
        },
        type: {
          type: String,
          value: "column"
        },
        avgData: {
          type: Array,
          value: [ ["Date", "Professionalism", "Skills", "Work Completion" ],
                             ["Jan 16", 40, 40, 40 ],
                             ["Feb 16", 40, 40, 40 ],
                             ["Mar 16", 40, 40, 40 ],
                             ["Apr 16", 40, 40, 40 ],
                             ["May 16", 40, 40, 40 ],
                             ["Jun 16", 40, 40, 40 ],
                             ["Jul 16", 40, 40, 40 ],
                             ["Aug 16", 40, 40, 40 ],
                             ["Sep 16", 40, 40, 40 ],
                             ["Oct 16", 40, 40, 40 ] ],
          notify: false
        }
      },

      computeData: function(avgData, userData){
        console.log(userData[1][0])
        var data = userData.slice();
        console.log(userData[1][1])
        console.log(avgData[1][1])
        console.log(userData[1][1]-avgData[1][1])
        data[1][1] = userData[1][1] - avgData[1][1]
        return data;
      }
  });
  })();
  </script>
</dom-module>

UPDATE: Per Günter Zöchbauer's suggestion I have changed var data = userData; to var data = userData.slice();. However this has not yet solved the issue. There seems to be something else that I am missing here.

UPDATE: After looking at the userData after data was modified it appears that it was still pointing to the same object. I changed var data = userData.slice(); to

    var data = [];
    for (var i = 0, len = userData.length; i < len; i++) {
      data[i] = userData[i].slice();
    }

in order to solve this issue. This has solved the issue I was encountering but still leads me to wonder why userData was being modified from a different tag. If someone could comment and let me know why it would be very appreciated.

2 Answers2

2

I have the suspicion you expect this line

var data = userData;

to create a copy of userData but after this line, data and userData point to the same data structure.

Try instead

var data = userData.splice();

Just a guess. If this isn't the issue I don't know what causes the problem.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • This will help me the with immediate need of not changing userData. However, I do not understand why changing userData would even change the userData in other tags. When the next tag changes userData why is it already changed? – Daniel Robert Miller Feb 01 '16 at 06:59
  • Actually I'm not sure I understand your problem, I just suspected this to be the issue. When several places reference the same array instance and one of them makes changes, everywhere else they reference this same array instance, see the modifications. If you only want to change the array at once place, the different references need to point to their own copy of the array. `.splice()` creates such a copy. It seems you expect a copy of your array being passed instead of a reference to the original array. This is not the case. – Günter Zöchbauer Feb 01 '16 at 07:06
  • After trying your suggestion I found that data came back as an empty array `[]`. I did a little digging an found I could also use `.slice()` in the same manner. I tried that and it is still giving me the same output as when I was not using it. Is there something that `.splice()` would do here that `.slice()` is not doing for me? – Daniel Robert Miller Feb 01 '16 at 15:27
  • http://stackoverflow.com/questions/3978492/javascript-fastest-way-to-duplicate-an-array-slice-vs-for-loop I don't develop in JS myself I use only Dart. I mostly tried to communicate the idea of shared reference and a copy. Can you please update your question to show the current version of your code? – Günter Zöchbauer Feb 01 '16 at 15:30
  • 1
    Up voted: I was looking at [link](http://ariya.ofilabs.com/2014/02/javascript-array-slice-vs-splice.html) and it looks like `.slice()` is what you mean. I appreciate the idea as this was a major oversight on my part. – Daniel Robert Miller Feb 01 '16 at 15:37
  • 1
    I guess the problem is still the same but just a level deeper. After `var data = userData.slice();` `data` and `userData` are different arrays, but their elements are arrays as well and `slice()` doesn't do a deep copy, only a shallow copy. This means the elements of `userData` and `data` are just references to the same arrays. You need to run `slice()` recursively. – Günter Zöchbauer Feb 01 '16 at 15:51
1

This could possibly be it, but I can't remember that it worked when I tried it myself. My emphasis in italics below.

Configuring default property values

Default values for properties may be specified in the properties object using the value field. The value may either be a primitive value, or a function that returns a value.

If you provide a function, Polymer calls the function once per element instance.

When initializing a property to an object or array value, use a function to ensure that each element gets its own copy of the value, rather than having an object or array shared across all instances of the element.

  data: {
    type: Object,
    value: function() { return {}; }
  }

Source: https://www.polymer-project.org/1.0/docs/devguide/properties#configure-values

Rickard Elimää
  • 7,107
  • 3
  • 14
  • 30
  • And why the accepted answer works is because you create a new array with `slice`, and therefor cuts the reference to the original array. – Rickard Elimää Jan 30 '19 at 08:40