2
votes

I'm having difficulty to get parent component's property object, with dynamically populated properties to make the values available inside of the same component.

A bit hard to explain, so please have a look at the example below:

Parent Component

<script>
    export default {
        data() {
            return {
                fields: {},
            }
        }
    }
</script>

Child Component

<template>
  <select
      @change="update()"
      v-model="field"
  >
      <option
          v-for="option in options"
          :value="option.value"
      >
          {{ option.name }}
      </option>
  </select>
</template>
<script>
    export default {
        props: {
            initialOptions: {
                type: Array,
                required: true
            }
        },
        data() {
            return {
                    field: '',
                options: this.initialOptions
            }
        },
        mounted() {
            if (
                (this.field === undefined || this.field === '') &&
                this.options.length > 0
            ) {
                this.field = this.options[0].value;
            }
            this.update();
        },
        methods: {
            update() {
                this.$emit('input', this.field);
            }
        }
    }
</script>

DOM

<parent-component inline-template>

    <div>

        <child-component>           
            :initial-options="[{..}, {..}]"
          v-model="fields.type_id"
        ></child-component>

    </div>

    <div :class="{ dn : fields.type_id == 2 }">

        // ...

    </div>

</parent-component>

Using Vue console I can see that fields object gets all of the child component models with their associated values as they emit input when they are mounted, however for some strange reason the :class="{ dn : fields.type_id == 2 }" does not append the class dn when the selection changes to 2. Dom doesn't seem to reflect the changes that are synced between parent and child components.

Any help on how to make it work?

2
How are you adding properties to the parent fields?Bert
Using v-model directive on the child component and on mounted emitting input event with associated value from each child component.Sebastian Sulinski
Can you show it? What I'm getting at is fields is starting off as an empty object. If you are adding properties to that incorrectly, then Vue will not be able to detect the changes.Bert
It is in the example above - please check Child Component section and then DOM where child component gets v-model. You'll see that on mounted there is a call to update() method, which emits the event.Sebastian Sulinski
When/how does fields: {} get a property called type_id?Bert

2 Answers

3
votes

Here is what I was trying to get at in comments. Vue cannot detect changes to properties that are added dynamically to an object unless you add them using $set. Your fields object does not have a type_id property, but it gets added because you are using v-model="fields.type_id". As such, Vue does not know when it changes.

Here, I have added it and the color of the text changes as you would expect.

console.clear()

Vue.component("child-component", {
  template: `
    <select
        @change="update()"
        v-model="field"
    >
        <option
            v-for="option in options"
            :value="option.value"
        >
            {{ option.name }}
        </option>
    </select>
  `,
  props: {
    initialOptions: {
      type: Array,
      required: true
    }
  },
  data() {
    return {
      field: '',
      options: this.initialOptions
    }
  },
  mounted() {
    if (
      (this.field === undefined || this.field === '') &&
      this.options.length > 0
    ) {
      this.field = this.options[0].value;
    }
    this.update();
  },
  methods: {
    update() {
      this.$emit('input', this.field);
    }
  }
})

new Vue({
  el: "#app",
  data: {
    fields: {
      type_id: null
    }
  }
})
.dn {
  color: red;
}
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<div id="app">
  <div>
    <child-component :initial-options="[{name: 'test', value: 1}, {name: 'test2', value: 2}]" v-model="fields.type_id"></child-component>
  </div>
  <div :class="{ dn : fields.type_id == 2 }">
    Stuff
  </div>
</div>
0
votes

It looks like you are trying to make a re-usable component.

I would ask myself what the value of a re-usable component is when the parent component has to handle more than half of the effort. The component might be better named...

 <DifficultToUseSelect/>.

Essentially, you are creating a component that provides, all by itself, all of the following HTML...

<select></select>

Everything else is managed by the parent component.

It would probably be more useful to do any of the following...

  • Encapsulate often needed options in a specific select component, as in

    StateAbbrevsSelect v-model="state"

  • Pass the name of a data model to a select component. The component would then load and manage its own data via the model.

  • Pass the URL of a web service to the component, which it then calls to load its options.

Again, the main point I am trying to convey here is that making a re-usable component where more than half of the effort is handled by the parent component is really not very re-usable.