In this article I’ll share with you the solutions I’ve found to a small issue I’ve encountered: using form input elements and the
v-model attribute, in scoped slots.
Scoped slots and v-model
VueJS provides a pretty useful feature to make your code DRY (Don’t repeat yourself): slots. If you are not aware of what slots are, I’ll give you an example. Dialog box is a good one. A dialog box always behaves the same way. When you open it, it creates a view appearing above the current view. This new view usually has a title, a content, and some buttons. And the content could be anything! Some raw text, a form, a picture, a combination of those things, some other things… In this case, would you develop a new dialog box for each sort of content? The answer better be “Hell no!”. And slots would be your tool for the job. You’d create the “frame” component, which would take care of displaying a view over the previous one, and use a slot for the content.
But you probably already know all of that. What this article will be about, is one particular case I’ve had to handle: input elements in slots. Form input elements is a very interesting case because VueJS provides a particular attribute for them:
v-model. An input field using
v-model can’t work that way
The example I’ll use for this article is a custom dynamic form. Let’s say we want to build a form with input elements like
<textarea> (which are eligible for
v-model). And with each of these control, we want to display a warning if it is empty. One option would be to create two new components based on
<input> for one and
<textarea> for the other, adding this new feature, and then use those two new components in a form. One better option would be to create only one new component, let’s call it
FormElement, which would implement this new feature, and would have a slot for the form input element.
Let’s create this FormElement:
In the slot we pass the field value variable so it can be used by the
In our form we will use the v-model keyword on both
<textarea> elements, because we always do:
But sadly this won’t work. In fact it won’t even compile. You will get the following error message:
'v-model' directives cannot update the iteration variable 'value' itself
Well, we could have foreseen this. The values passed using scoped slots are read-only. So
v-model can’t write to it.
There is actually lots of solutions to this issue; I’ll give you my two favorites.
Solution 1: Exploding
v-model is one of the iconic feature of VueJS. It’s part of the first thing we’re taught to use when learning the framework. And that’s probably the reason why we forget (or even don’t know) how it works. As stated in the official documentation,
v-model replace the use of both
@input. That is how the magic happens. When the linked variable changes(
:value), the value of the input is updated, and when the user types something in the input (
@input), the variable is updated.
So what we could do is use this exploded form of the
v-model, and pass a function in the scoped slot to update the variable.
Here is what it would look like:
I like this solution because it’s easy to understand at first glance. It keep things neat.
Solution 2: Passing the parent in the scoped slot
Another option, and I like this one because it looks smart, is not to pass the value variable in the slot, but the object containing that variable as a property. In our example we would pass the
field property. And in the slot, we would set
const), you won’t be allowed to change the value of it, but you will be allowed to change its properties’ values.
This one is more compact and there’s no need to declare a method to manage the input event. The less code you write, the less mistakes you make!
Thanks for reading me. I hope you enjoyed this article.
Please comment and share your way of handling v-model in scoped slots.
Have a nice day!