Home > OS >  Vue: Update part of an html string from data v-for loop with v-model input
Vue: Update part of an html string from data v-for loop with v-model input

Time:06-26

Using Vue2 I have an array of objects in data which have an html string rendered in a v-for loop. Part of each string is a prop, which renders correctly initially. However, when the prop value is updated with v-model the data in the v-for loop is not updated.

jsfiddle: When the input is changed from "Bob" to "Sally" all instances should change, but those in the for-loop do not.

html

<div id="app">
  <h2>Testing</h2>
  <ul>
    <li v-for="statement in statements" v-html="statement.text"></li>
  </ul>
  
  <input v-model="name" placeholder="edit name">
  <p>Name is: {{ name }}</p>
  
  <p >Outside loop: <b>{{name}}</b> likes dogs.</p>
</div>

vue

new Vue({
  el: "#app",
  data: function() {
    return {
      statements: [
          {
            id: 'food',
            text: '<b>'  this.name   '</b> likes to eat ice cream.',
          },
          {
            id: 'fun',
            text: 'Running is the favorite activity of <b>'  this.name   '</b>',
          },
      ],
    }
  },
  
  props: { 
    name: {
      type: String,
      default: 'Bob',
    },
  },
})

The code has been simplified - the actual HTML strings have ~3 variables each that need to update, and are at different locations in each string, so I can't think of another way to replace the values when they are updated, while preserving the html tags. This is intended to be a single-page vue application, but is using Laravel and blade for some general page formatting.

CodePudding user response:

If I understood you correctly try like following snippet:

(you should not mutate props, emit event from child component and update your props)

Vue.component('Child', {
  template: `
  <div >
    <h2>Testing</h2>
    <ul><li v-for="statement in statements" v-html="statement.text"></li></ul>
    <input v-model="cname" @input="$emit('namechanged', $event.target.value)" placeholder="edit name">
    <p>Name is: {{ cname }}</p>
    <p >Outside loop: <b>{{ cname }}</b> likes dogs.</p>
  </div>`,
  props: { 
    name: {
      type: String,
      default: 'Bob',
    },
  },
  data() {
    return {
      cname: this.name,
    }
  },
  computed: {
    statements() {
      return [{id: 'food', text: '<b>'  this.cname   '</b> likes to eat ice cream.',}, {id: 'fun', text: 'Running is the favorite activity of <b>'  this.cname   '</b>',},]
    }
  },
})

new Vue({
  el: '#demo',
  data() {
    return {
      pname: ''
    }
  },
  methods: {
    updateName(val) {
      this.pname = val
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
  <child @namechanged="updateName"></child>
</div>

CodePudding user response:

There are several problems with what you've shown so far.

  • you're trying to mutate a prop. If you do that, when the value in the parent updates, your change will be overridden. So you need to update the parent. Use .sync.
  • this inside data is not what you think it is. Use computed to compute a value based on other reactive props of your component. When the reactive references change, your computed gets updated as well
  • your looping without keys
  • you're using v-html (it's a security risk). You're better off using markdown in this case.

Another problem is you probably simplified your example too much. I doubt you have props on the app itself, you probably have a <Person /> component...

Here's how something like this would work:

Vue.component('person', {
  template: `#person-tpl`,
  props: {
    person: {
      type: Object,
      default: () => ({})
    }
  },
  data: () => ({
    inputs: [
      { name: 'name', label: 'Name', placeholder: 'Bob' },
      { name: 'food', label: 'Favorite food', placeholder: 'icecream' },
      { name: 'fun', label: 'Favorite activity', placeholder: 'Running' }
    ]
  }),
  computed: {
    name: {
      get() { return this.person?.name || '' },
      set(name) { this.update({ name }) } 
    },
    fun: {
      get() { return this.person?.fun || '' },
      set(fun) { this.update({ fun }) }
    },
    food: {
      get() { return this.person?.food || '' },
      set(food) { this.update({ food }) }
    },
    statements() {
      return [{
        id: 'food',
        text: `<b>${this.name}</b> likes to eat ${this.food}.`
      }, {
        id: 'fun',
        text: `${this.fun} is the favourite activity of <b>${this.name}</b>`
      }]
    }
  },
  methods: {
    update(payload) {
      this.$emit('update:person', {
        ...this.person,
        ...payload
      });
    }
  }
})

new Vue({
  el: "#app",
  data: () => ({
    persons: [
      {name: 'Jane', food: 'apples', fun: 'Swimming'},
      {},
      {name: 'John', food: 'milk', fun: 'Sleeping' }
    ]
  }),
  methods: {
    updatePerson(key, val) {
      this.persons.splice(key, 1, val)
    }
  }
})
<script src="https://v2.vuejs.org/js/vue.min.js"></script>
<div id="app">
  <h2>Testing</h2>
  <person v-for="(person, key) in persons" :key="key"
          :person.sync="person"
          @update:person="val => updatePerson(key, val)" />
</div>

<template id="person-tpl">
  <div>
    <ul>
      <li v-for="(statement, key) in statements" :key="key" v-html="statement.text"></li>
    </ul>
    <template v-for="prop in inputs">
      <label :key="prop.name">{{ prop.label }}
        <input :value="person[prop.name]"
               :placeholder="prop.placeholder"
               @input="e => update({[prop.name]: e.target.value })">
      </label>
    </template>
  </div>
</template>

  • Related