Pablo Veiga
Posted on • Updated on
#vue #javascript #frontend #beginners
VueJS is a web framework used to build front-end applications and it is widely adopted by web developers around the world.
It provides the v-model
directive that makes two-way binding between form inputs "a breeze".
Depending on what you are building, you might need to build custom components that deal with two-way data binding. Here are some ways of implementing your own custom v-model
:
- Local variable watcher
- Custom method
- "Powerful" computed property
- Custom prop and event (VueJS 2)
- The .sync modifier (VueJS 2)
Obs.: The goal here is not to benchmark neither discuss which of the implementations is the best but to introduce the different approaches that can be used to implement v-model
in your custom components.
ℹ The component named BaseInput.vue
used in the examples is very simple, and you might even question if implementing a custom v-model
is really necessary for it, but, as mentioned, the intention is just to demonstrate the possibilities.
1. Local variable watcher
This is probably the most used way of implementing v-model
in your custom components. You create a prop named value
using the type you need, then create a local observable variable in data()
and initialize it with the value of the prop you've created previously and watch its changes in order to emit an input
event to the parent component to update the value
prop from the outside**.
<!-- BaseInput.vue --><template> <input type="text" v-model="model" /></template><script> export default { props: { value: { type: String, default: '' } }, data() { return { model: this.value } }, watch: { model(currentValue) { this.$emit('input', currentValue) } } }</script><!-- Usage --><BaseInput v-model="text" />
2. Custom method
You might have already read that, to prevent performance issues, you should avoid using watchers in your application.
In this second example, we take advantage of the @input
event triggered by the native input
element* and, using a custom method inside our component, we pass the value of the input to the parent component emitting an input
event so that the value
prop is updated from the outside**.
It is also important to mention that in this case we do not use the v-model
in the native input, but the value
attribute.
* VueJS already attaches event listeners to form inputs for us automatically and when these inputs are destroyed, all listeners are destroyed as well
<!-- BaseInput.vue --><template> <input type="text" :value="value" @input="onInput" /></template><script> export default { props: { value: { type: String, default: '' } }, methods: { onInput(event) { this.$emit('input', event.target.value) } } }</script><!-- Usage --><BaseInput v-model="text" />
⚠ VueJS 3: if you are using the latest version of VueJS, change the name of the prop from value
to modelValue
and the name of the event from input
to update:modelValue
as per VueJS 3 docs
3. "Powerful" computed property
Another way of implementing v-model
in your custom component is using computed properties getters and setters.
You can define a local computed property, implement a getter that returns the value
property, and a setter that emits an input
event for the parent component to update the value
prop from the outside**.
<!-- BaseInput.vue --><template> <input type="text" v-model="model" /></template><script> export default { props: { value: { type: String, default: '' } }, computed: { model: { get() { return this.value }, set(value) { this.$emit('input', value) } } } }</script><!-- Usage --><BaseInput v-model="text" />
⚠ VueJS 3: if you are using the latest version of VueJS, change the name of the prop from value
to modelValue
and the name of the event from input
to update:modelValue
as per VueJS 3 docs
** You must avoid changing a prop value directly See Docs.
4. Custom prop and event (VueJS 2)
You might have noticed that, in the previous examples, the name of the prop is always value
and the name of the event is always input
. These are defaults to implement a v-model
in your custom component. But you can change it if you want. You can name the prop and the event according to your own needs.
For that to be possible you may set the model
attribute and tell the component which names you expect to represent the prop and the event that will update it.
<!-- BaseInput.vue --><template> <input type="text" :value="text" @input="onInput" /></template><script> export default { model: { prop: 'text', event: 'update' }, props: { text: { type: String, default: '' } }, methods: { onInput(event) { this.$emit('update', event.target.value) } } }</script><!-- Usage --><BaseInput v-model="text" />
⚠ VueJS 3: if you are using the latest version of VueJS, this approach will not work since it is now deprecated
5. The ".sync" modifier (VueJS 2)
This is not a v-model
implementation exactly but it will work as it is. With the .sync
modifier (VueJS 2.3+), the child component doesn’t need a value prop. Instead, it uses the same prop name you synced in the parent.
Also instead of emitting an input
event to update the prop, you emit the conveniently named event update:text
. (Source: Vue’s new and improved prop.sync).
<!-- BaseInput.vue --><template> <input type="text" :value="text" @input="onInput" /></template><script> export default { props: { text: { type: String, default: '' } }, methods: { onInput(event) { this.$emit('update:text', event.target.value) } } }</script><!-- Usage --><BaseInput :text.sync="text" />
⚠ VueJS 3: if you are using the latest version of VueJS, this approach will not work since it is now deprecated
6. Named v-model (VueJS 3)
With VueJS 3, released on 18 September 2020, it is now possible to define which prop will represent the v-model
inside the component in an extremely easy way.
To do that, you just need to use a modifier in the v-model
itself when using your custom component.
In the example below, we are defining that the text
prop, inside the BaseInput
component will receive the value from the v-model
.
<!-- BaseInput.vue --><template> <input type="text" :value="text" @input="onInput" /></template><script> export default { model: { prop: 'text', event: 'update' }, props: { text: { type: String, default: '' } }, methods: { onInput(event) { this.$emit('update', event.target.value) } } }</script><!-- Usage --><BaseInput v-model:text="text" />
Let me know if you know of any other implementation that might be worth mentioning or event provide me suggestions about subjects that can become short articles like this one.
You can find an example for all of the mentioned approaches in this repo.
Thanks to @keithmchd48 for his help! (Check comments)
I hope it is useful and please, share it!
Top comments (9)
Subscribe
LuisJo1
LuisJo1
-
Joined
• May 11 '22
- Copy link
Thank you very much for sharing your knowledge, it was very useful, greetings
srikanth597
srikanth597
Technology enthusiastic person, majorly focusing on CloudNative Software Development, DevOps & Automations in Public Cloud AWS
-
Location
India
-
Work
Software Engineer II at Intuit
-
Joined
• Apr 17 '21 • Edited on Apr 17 • Edited
- Copy link
Hey good article.
But in your 1,2,3 you wrote Baseinput v-model="text" .
And in component props, it was written as
value:String
Which doesn't sound correct to me , was it a typo?.
In 4 & 5 also , emit event is not declared on component definition and also when u call Baseinput u were not specifying the custom emit event which is "update".
U just used v-model everywhere, is this correct?
Pablo Veiga
Pablo Veiga
Software Engineer since 2013 | JavaScript expert | VueJS Certified Developer |Agile developer | Amateur writer | Beer and soccer lover
-
Email
vcpablo@gmail.com
-
Location
Brazil
-
Pronouns
him/his
-
Work
Software Engineer at Spectora
-
Joined
• Apr 20 '21
- Copy link
Hey @srikanth597 thanks for reading and participating.
Explaining 1, 2 and 3.
By default, v-model
on a component uses value
as the prop and input
as the event. So that's why I use v-model
on <BaseInput />
usage and, inside it, there is a prop called value
. The value of the P text
variable will be bind directly to the default prop value
.
(Check: Customizing Component v-model (VueJS docs))
In regards to 4 and 5, as a component is, by default, a reusable instance Vue
, therefore I don't need to define the $emit
event manually, because it is already present in the component implicitly.
(Check: Base Example (VueJS docs)
Now, talking about the update
event, in both 4th and 5th examples, the model
property is responsible to configure the update
event, as the one that will update the v-model
, which is, in these cases, represented by the text
prop.
(Check: Model (VueJS docs
Cheers!
srikanth597
srikanth597
Technology enthusiastic person, majorly focusing on CloudNative Software Development, DevOps & Automations in Public Cloud AWS
-
Location
India
-
Work
Software Engineer II at Intuit
-
Joined
• Apr 21 '21
- Copy link
Thanks for ur reply,
So if I understood correctly Vue automatically figure out v-model='text' as value prop automatically in component definition.
This arises wierd doubt in my mind, what happens if for some reason I want to have 2 v-model properties I need to pass to component.
I understood your 4&5 point explanation
Pablo Veiga
Pablo Veiga
Software Engineer since 2013 | JavaScript expert | VueJS Certified Developer |Agile developer | Amateur writer | Beer and soccer lover
-
Email
vcpablo@gmail.com
-
Location
Brazil
-
Pronouns
him/his
-
Work
Software Engineer at Spectora
-
Joined
• Apr 21 '21
- Copy link
Hey @srikanth597, in the latest released version of VueJS, v3, it is possible to bind multiple v-model’s
Take a look at this article to understand a bit better.
dev.to/thomasfindlay/how-to-easily...
Keith Machado
Keith Machado
Frontend Developer - Vue, JS
-
Location
Mumbai
-
Work
Frontend Developer
-
Joined
• Apr 24 '21 • Edited on Apr 24 • Edited
- Copy link
Hey Pablo, I experimented with all these ways with Vue 3 and here is what I found out:
1 and 6 are the only ones which worked for me.
For 2, 3, 4, and 5, they did not work with the code you have posted in this blog.
One minor update for #6, I get this error when I try to use the ".sync" modifier.
'.sync' modifier on 'v-bind' directive is deprecated. Use 'v-model:propName'
You can check out my code in this repo:
github.com/keithmchd48/v-model-ways
Live example of all these different implementations:
v-model-different-ways.netlify.app/
Pablo Veiga
Pablo Veiga
Software Engineer since 2013 | JavaScript expert | VueJS Certified Developer |Agile developer | Amateur writer | Beer and soccer lover
-
Email
vcpablo@gmail.com
-
Location
Brazil
-
Pronouns
him/his
-
Work
Software Engineer at Spectora
-
Joined
• Apr 27 '21
- Copy link
Hey @keithmchd48,
I have updated the article mentioning the breaking changes in VueJS 3 and have also added a link to a repo with all (possible) implementations for both versions 2 and 3 of VueJS.
Thank you very much for your help and kindness.
Jonathan Machado
Jonathan Machado
-
Location
Staffordshire
-
Work
Web developer
-
Joined
• Apr 20 '21
- Copy link
Great article!
Pablo Veiga
Pablo Veiga
Software Engineer since 2013 | JavaScript expert | VueJS Certified Developer |Agile developer | Amateur writer | Beer and soccer lover
-
Email
vcpablo@gmail.com
-
Location
Brazil
-
Pronouns
him/his
-
Work
Software Engineer at Spectora
-
Joined
• Apr 20 '21
- Copy link
Thanks, @jonathanfmachado! I really appreciate it!
For further actions, you may consider blocking this person and/or reporting abuse