Motivation
When I was using Vue2 along with vue-class-component
and vue-property-decorator
it was easy to synchronize v-models
between components simply using @ModelSync()
. When Vue 3 came out with its Composition API another way was needed to achieve the same result as if Class Component was used.
Implementation
If you're already familiar with the capabilities of Composition API, then simply use computed
within setup
to update the modelValue
whenever it changes.
1) In child component define a model property with the default value
import { defineComponent } from 'vue';
<script>
export default defineComponent({
name: 'FancyComponent',
props: {
modelValue: { // Declare the property
type: Object,
default: () => ({}), // Do not forget about default value
},
}
});
</script>
2) In setup()
define a computed property and expose it:
<template>
<div>
<input v-model="theModel.foo" /> <!-- Usage of model -->
</div>
</template>
<script>
import { defineComponent, computed } from 'vue';
export default defineComponent({
name: 'FancyComponent',
emits: ['update:modelValue'], // The component emits an event
props: {
modelValue: {
type: Object,
default: () => ({}),
},
},
setup(props, { emit }) {
const theModel = computed({ // Use computed to wrap the object
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value),
});
return { theModel };
}
});
</script>
3) In parent component use v-model
directive:
<template>
<FancyComponent v-model="someObject" />
</template>
TypeScript
In the case of using TypeScript, there is one minor addition to the code above. PropType<T>
is used it order to annotate the model type.
<script>
import { defineComponent, computed, PropType } from 'vue';
interface OurModelType {
foo: string;
}
export default defineComponent({
name: 'FancyComponent',
emits: ['update:modelValue'],
props: {
modelValue: {
type: Object as PropType<OurModelType>, // Type Annotation
default: () => ({}),
},
},
setup(props, { emit }) {
const theModel = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value),
});
return { theModel };
}
});
</script>
And it's all you need to know to pass reactive objects into your custom components as v-model
.
Top comments (13)
In the 'get' of computed, is the reactivity deeply? I used this way back in vue2, turn out I had to use
watch
with deep option to make it work.It depends. If you define new properties - you probably want to use
Vue.set
or$vm.set
. Vue 2 works using patching mechanism that modify object fields to use getters and setters, and it's done during initialization. Any other field won't be reactive if you don't set it with options written above.So it all depends on if you're creating completely new objects or adjust some properties you probably want to use
Vue.set
orObject.assign({}, oldObject, ...)
etc.You could find more descriptive information with examples in the official documentation.
That's make sense, thanks
Actually that setter inside computed
theModel
will never run, which you can easily prove by commenting out thatemit
statement inside of it, becausetheModel.foo
on input will be changed by mutating that model and not re-assigning over itHey, so if the setter function never runs then how come the value in the parent component gets updated? And if I want to call a function in the setter how do I do that?
You saved my day, I was struggling a lot with this XD Everytime I emitted the updated event I was getting an undefined object error, the default option saved me =D
In my case I am only using the emit event inside a watch (cuz I have a custom condition to update other property of the model). Vue seems to already send the emit event by default so no need to do it manually everytime.
Thank you so much buddy!
I'm happy to be helpful!
What if I need to validate the value first or make adjustments before emit()ing?
You could extend the setter.
For example:
The setter function does not run as the theModel key is bind to the input field not the theModel.
After couple of hours, I found your solution and it works flawlessly! Thank you!
I'm glad that the article was helpful!