3

I have been able to accomplish a single level deep of v-model two-way binding on a custom component, but need to take it one level deeper.

Current working code:

    <template lang="html">

      <div class="email-edit">

        <input ref="email" :value="value.email" @input="updateInput()"/>
<input ref="body" :value="value.body" @input="updateInput()"/>

      </div>

    </template>
    <script type="text/javascript">
      import LineEditor from './LineEditor.vue'
      export default {
        components: {
          LineEditor
        },
        computed: {
        },
        methods: {
          updateInput: function(){
            this.$emit('input',{
              email: this.$refs.email.value,
              body: this.$refs.body.value
            })
          }
        },
        data: function(){
          return {}
        },
        props: {
          value: {
            default: {
              email: "",
              body: ""
            },
            type:Object
          }
        }
      }
    </script>

Used like this: <email-edit-input v-model="emailModel" />

However, if I add this piece, the value no longer propagates upwards:

      <div class="email-edit">

        <line-editor ref="email" :title="'Email'" :value="value.email" @input="updateInput()"/>
<input ref="body" :value="value.body" @input="updateInput()"/>

      </div>

    </template>
    <script type="text/javascript">
      import LineEditor from './LineEditor.vue'
      export default {
        components: {
          LineEditor
        },
        computed: {
        },
        methods: {
          updateInput: function(){
            this.$emit('input',{
              email: this.$refs.email.value,
              body: this.$refs.body.value
            })
          }
        },
        data: function(){
          return {}
        },
        props: {
          value: {
            default: {
              email: "",
              body: ""
            },
            type:Object
          }
        }
      }
    </script>

Using this second custom component:

<template lang="html">

  <div class="line-edit">
    <div class="line-edit__title">{{title}}</div>
    <input class="line-edit__input" ref="textInput" type="text" :value="value" @input="updateInput()" />
  </div>

</template>
<script type="text/javascript">
  export default {
    components: {
    },
    computed: {
    },
    methods: {
      updateInput: function(){
        this.$emit('input', this.$refs.textInput.value)
      }
    },
    data: function(){
      return {}
    },
    props: {
      title:{
        default:"",
        type:String
      },
      value: {
        default: "",
        type: String
      }
    }
  }
</script>

The first code-block works fine with just an input. However, using two custom components does not seem to bubble up through both components, only the LineEditor. How do I get these values to bubble up through all custom components, regardless of nesting?

2 Answers 2

6

I've updated your code a bit to handle using v-model on your components so that you can pass values down the tree and also back up the tree. I also added watchers to your components so that if you should update the email object value from outside the email editor component, the updates will be reflected in the component.

console.clear()

const LineEditor = {
  template:`
    <div class="line-edit">
      <div class="line-edit__title">{{title}}</div>
      <input class="line-edit__input" type="text" v-model="email" @input="$emit('input',email)" />
    </div>
  `,
  watch:{
    value(newValue){
      this.email = newValue
    }
  },
  data: function(){
    return {
      email: this.value
    }
  },
  props: {
    title:{
      default:"",
      type:String
    },
    value: {
      default: "",
      type: String
    }
  }
}

const EmailEditor = {
  components: {
    LineEditor
  },
  template:`
    <div class="email-edit">
      <line-editor :title="'Email'" v-model="email" @input="updateInput"/>
      <input :value="value.body" v-model="body" @input="updateInput"/>
    </div>
  `,
  watch:{
    value(newValue){console.log(newValue)
      this.email = newValue.email
      this.body = newValue.body
    }
  },
  methods: {
    updateInput: function(value){
      this.$emit('input', {
        email: this.email,
        body: this.body
      })
    },
  },
  data: function(){
    return {
      email: this.value.email,
      body: this.value.body
    }
  },
  props: {
    value: {
      default: {
        email: "",
        body: ""
      },
      type: Object
    }
  }
}

new Vue({
  el:"#app",
  data:{
    email: {}
  },
  components:{
    EmailEditor
  }
})
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<div id="app">
  <email-editor v-model="email"></email-editor>
  <div>
    {{email}}
  </div>
  <button @click="email={email:'testing@email', body: 'testing body' }">change</button>
</div>

In the example above, entering values in the inputs updates the parent. Additionally I added a button that changes the parent's value to simulate the value changing outside the component and the changes being reflected in the components.

There is no real reason to use refs at all for this code.

Sign up to request clarification or add additional context in comments.

2 Comments

These seems to be a more complete solution. I've added it into my codebase, and it works. Just to be sure I follow your logic. Props pass down to the child, the child uses a watcher to catch any changes from the parent to the props and adjust its instance scoped variables accordingly. The instance scope variables, data, are used for the inputs as v-model to maintain what the user edits are, and pass up to the parent through the emit on input. Does this cover your logic chain?
@steventnorris You got it.
0

In my case, having the passthrough manually done on both components did not work. However, replacing my first custom component with this did:

<line-editor ref="email" :title="'Email'" v-model="value.email"/>
<input ref="body" :value="value.body" @input="updateInput()"/>

Using only v-model in the first component and then allowing the second custom component to emit upwards did the trick.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.