From Vue 2 to Vue 3: v-model modifiers
In our last lesson, we learned how to create a component that is capable of multiple v-model
bindings. We also used this component in our application, and applied two simultaneous bindings into an instance of the component.
This time around, we are going to learn another advanced capability of v-model
in Vue 3, the ability to create our own custom v-model
modifiers.
Let’s dive right in.
v-model: Make it special
You’ve probably already used v-model
modifiers before, Vue comes with quite a few out of the box.
- .lazy - listen to change events instead of
input
(for native inputs) - .number - cast valid input string to numbers
- .trim - trim input Note that all of these are still available for you in Vue 3.
Let’s learn how to create our own modifiers by building upon the example SalutationName
component that we created in the last lesson.
We are going to first add the ability to pass in a modifier called capitalize
to both of our bindings.
Here’s the twist:
For the salutation
binding, we’ll go ahead and capitalize the whole acronym.
For the name
binding, we’ll just capitalize the first letter.
We’ll start by adding the modifiers to our App.vue
, where the instance of SalutationName
is being used.
📃App.vue
<template>
<div id="app">
<SalutationName
v-model:salutation.capitalize="form.salutation"
v-model:name.capitalize="form.name"
/>
<pre>{{ form }}</pre>
</div>
</template>
[...]
Notice that custom modifiers are declared just the same as out-of-the box modifiers, by adding a .
after the v-model:model
declaration and the name of the modifier.
Vue 3 is now going to try to inject two new props
into our component.
salutationModifiers
for thesalutation
v-model binding.nameModifiers
for thename
v-model bindings.
Let’s update our SalutationName.vue
component and add these new props.
📃SalutationName.vue
[...]
props: {
salutation: {
type: String,
default: ''
},
salutationModifiers: {
default: () => ({}),
type: Object
},
name: {
type: String,
default: ''
},
nameModifiers: {
default: () => ({}),
type: Object
}
},
[...]
Notice that we are not declaring any particular default state for the capitalize
property of these objects. We simply declare that each of these properties will default to an empty object.
Modifiers will be added to these props as booleans, which means that if no modifiers are received by the component instance, they simply will remain an empty object.
If a modifier like capitalize
is added, the object will reflect it by adding a true
value to the respective modifier that was added.
{
capitalize: true
}
With this knowledge in mind, we are going to refactor our component. Let’s first move the $emit
declarations out of the template
, and make functions that will hold all of our logic.
📃SalutationName.vue
<template>
<div>
<select
name="salutation"
@change="updateSalutation"
>
<option value="">-</option>
<option
v-for="item of salutations"
:value="item"
:key="item"
:selected="salutation === item"
>
{{ item }}
</option>
</select>
<input
:value="name"
@input="updateName"
type="text"
name="name"
/>
</div>
</template>
<script>
const salutations = [...]
export default {
props: {
salutation: {
type: String,
default: ''
},
// Holds the modifiers for the salutation v-model
salutationModifiers: {
default: () => ({})
},
name: {
type: String,
default: ''
},
// Holds the modifiers for the name v-model
nameModifiers: {
default: () => ({})
}
},
setup (props, { emit }) {
const updateSalutation = event => {
let val = event.target.value
emit('update:salutation', val)
}
const updateName = event => {
let val = event.target.value
emit('update:name', val)
}
return {
salutations,
updateSalutation,
updateName
}
}
}
</script>
Notice that we updated the template
to reflect the two new functions — updateSalutation
and updateName
that we declared in the setup
function of our component. For now, it does the exact same thing as before.
Notice also that we have modified the setup()
function to accept a props
parameter as a first argument, and { emit }
from the second argument.
The second argument is the context
of the component instance, which in return holds an emit
property which has the $emit
function in it. We are using JavaScript deconstructing to extract only the emit
function directly.
Now that we’re done refactoring, we can add some logic inside our setup updateX
functions to modify the emitted value in case that a modifier is present. This is where the props
that we added earlier are going to shine, since now we can use a simple if statement to check if they are evaluating to true
and modify our emitted value.
📃SalutationName.vue
[...]
setup (props, { emit }) {
const updateSalutation = event => {
let val = event.target.value
if (props.salutationModifiers.capitalize) {
val = val.toUpperCase()
}
emit('update:salutation', val)
}
const updateName = event => {
let val = event.target.value
if (props.nameModifiers.capitalize) {
val = val.charAt(0).toUpperCase() + val.slice(1)
}
emit('update:name', val)
}
return {
salutations,
updateSalutation,
updateName
}
}
[...]
That’s it! Now we can go back to our browser and test our new modifiers in action.
Extra challenge
Are you up for a little fun? Try implementing an extra modifier for name
called reverse
that reverses everything the user types.
Tip: You’ll have to chain the modifiers together in the v-model
declaration like so:
v-model:name.capitalize.reverse="form.name"
Coming up next
In this lesson, you learned how to create and use your own modifiers for your v-model
ready components.
In our next lesson, we are going to take a deep dive into $attrs
, and some of the key differences between Vue 2 and Vue 3 regarding attributes like class
and style
.
We’ll also take a look at the disappearing act of $listeners
and what that means for us developers in terms of component composition.
See you in the next lesson!