Search code examples
vue.jsvuejs2vue-component

Vue parent component access to child component's computed Property


In Vue JS, I'm unable to watch for changes to an array when changes are made within an array element's (child's) computed Property.

I've boiled down the issue in a sample JSFiddle I wrote, so the example may not make sense logically but it does show my issue.

https://jsfiddle.net/trush44/9dvL0jrw/latest/

I have a parent component that holds an array of colors. Each color is rendered using a child component. The child component has a computed Property called 'IsSelected'. When the 'IsSelected' computed Property changes on any array element, I need to loop through the entire array to see if at least 1 element in the array is still selected then set IsAnyCheckboxChecked accordingly.

  1. Can you help me understand if I'm doing my computed and watch correctly?
  2. In the-parent component's watch, why does this.colors[i].IsSelected return undefined even though IsSelected renders just fine in the DOM?
<div id="app">
  Is any Color Selected?...... {{IsAnyCheckboxChecked}}
  <the-parent inline-template :colors="ColorList">
    <div>
      <the-child inline-template :color="element" :key="index" v-for="(element, index) in colors">
        <div>
          {{color.Text}}
          <input type="checkbox" v-model="color.Answer" />
          IsChecked?......{{IsSelected}}
        </div>
      </the-child>
    </div>
  </the-parent>
</div>
Vue.component('the-child', {        
    props: ['color'],
    computed: {
        IsSelected: function () {
            return this.color.Answer;
        }
    }
});

Vue.component('the-parent', {
    props: ['colors'],
    watch: {
        colors: {
            handler: function (colors) {
                var isAnyCheckboxChecked = false;

                for (var i in this.colors) {
                        // IsSelected is undefined even though it's a 'computed' Property in the-grandchild component
                    if (this.colors[i].IsSelected) { 
                        isAnyCheckboxChecked = true;
                        break;
                    }
                }
                this.$parent.IsAnyCheckboxChecked = isAnyCheckboxChecked;
            },
            deep: true
        }
    }
});

// the root view model
var app = new Vue({
    el: '#app',
    data: {
        'IsAnyCheckboxChecked': false,
        'ColorList': [
            {
                'Text': 'Red',
                'Answer': true
            },
            {
                'Text': 'Blue',
                'Answer': false
            },
            {
                'Text': 'Green',
                'Answer': false
            }
        ]
    }
});

Solution

  • use $refs for accessing the child directly. Inside a v-for ref becomes and array. since your v-for is based on this.color.length anyway use the same thing to loop though the value in the $ref var.

    https://jsfiddle.net/goofballtech/a6Lu4750/19/

    <the-child ref="childThing" inline-template :color="element" :key="index" v-for="(element, index) in colors">
    
    for (var i in this.colors) {
    
      if (this.$refs.childThing[i].IsSelected) { 
        isAnyCheckboxChecked = true;
        break;
      }
    }