Denis Lolik
Denis Lolik

Reputation: 329

Why isn't the prop reactive in the dynamic component?

I have a dynamic component being injected in the slot of another one and I pass the props object to it. But when I update the data (an array) which has been assosiated with the prop (dataT: this.tableData), that prop isn't being updated inside the component.

It seems like I have a deal with two different objects but the array was passed by the reference, wasn't it?

This is the main component


    <template>
       <Button @click="addWindows"></Button>
       <Window v-for="window in windows" :key="window.id">
          <component :is="window.name" v-bind="window.props" @onDeleteRow="handleDeleteRow"></component>    
       </Window>
    </template>


    <script>
        export default{
           data(){
              return{
                 windows:[],
                 tableData:[
                    {
                       id: '0',
                       name: 'dog'
                    },
                    {
                       id: '1',
                       name: 'cow'
                    },
                    {
                       id: '2',
                       name: 'cat'
                    }
                 ]
              }
           },  
           methods:{
              addWindows(){
                 this.windows = [
                 {
                    id: 0,
                    name: 'Component1',
                    props: {
                       dataT: this.tableData
                    }
                 }, 
                 {
                    id: 1,
                    name: 'Component2',
                    props: {}
                 }];
              },
              handleDeleteRow(id){
                 this.tableData = this.tableData.filter(r => r.id != id);
              }
           }
        }
    </script>

I expect updating dataT prop in Component1 when I modify this.tableData in the main component.

Upvotes: 1

Views: 721

Answers (2)

skirtle
skirtle

Reputation: 29092

Original answer based on an earlier version of the question

If you make windows a computed property it can depend on tableData:

export default {
  data() {
    return {
      tableData: [
        {
          id: '0',
          name: 'dog'
        },
        {
          id: '1',
          name: 'cow'
        },
        {
          id: '2',
          name: 'cat'
        }
      ]
    }
  },
  computed: {
    windows () {
      return [
        {
          id: 0,
          name: 'Component1',
          props: {
            dataT: this.tableData
          }
        }, {
          id: 1,
          name: 'Component2',
          props: {}
        }
      ]
    }
  }
}

If you can't make all of it a computed property, e.g. because you need to be able to modify it, then keep it as data and just use the computed property to create the array needed in your template. In that case the computed property would just be merging together different parts of the data into the correct form.

In your original code, the line dataT: this.tableData won't work because this.tableData doesn't exist yet, it'll just be undefined. There's no lazy evaluation here, it needs to resolve to the correct object at the point it hits that line.

Even if it was able to get access to the correct object it wouldn't help because in handleDeleteRow you're reassigning tableData to point to a different object. Passing 'by reference' has nothing to do with the name you use to identify the object, it refers to a reference in memory.

Incidentally, v-on also supports an object syntax, just like v-bind, so you could make onDeleteRow optional in a similar fashion.

Update based on the edited question

When you write this in addWindows:

props: {
    dataT: this.tableData
}

This will assign the current value of this.tableData to dataT. That current value will be an array and as arrays are reference types any modifications made to that array will apply no matter what identifier is used to reference it.

However, this line...

this.tableData = this.tableData.filter(r => r.id != id);

... does not modify that array. Instead it assigns a totally new array to this.tableData. This will have no effect on the array referenced by dataT, which is unchanged.

There are several ways you could approach solving this, including using a computed property. However, a property getter might provide a convenient sleight-of-hand:

addWindows () {
  const vm = this;

  this.windows = [
    {
      id: 0,
      name: 'Component1',
      props: {
        get dataT () {
          return vm.tableData
        }
      }
    },
    {
      id: 1,
      name: 'Component2',
      props: {}
    }
  ];
}

This will always evaluate to the current value of tableData. Vue's reactivity should be fine with this extra indirection, it just sees it as equivalent to accessing tableData directly.

Upvotes: 4

Aviad
Aviad

Reputation: 3584

TL;DR

The issue is with your binding. Use the following:

<component 
    :is="window.name"
    :dataT="window.props.dataT"
    @onDeleteRow="handleDeleteRow">
</component>

Explanation

the v-bind attribute specifies what prop is bound to what value (or reference). In your case, you didn't specify what values you're binding to what props, thus the component props weren't bound as expected.

Upvotes: 1

Related Questions