We all had a case where we needed to reuse a lot of HTML code. Maybe a form containing multiple inputs with their label. That's why we would create a component for that.
import Vue from "https://unpkg.com/vue/dist/vue.esm.browser.js";
const InputLabel = {
template: `
<label>
<span>
<slot></slot>
</span>
<input type="text">
</label>
`
};
new Vue({
el: "#vue",
components: {
InputLabel
},
template: `
<input-label>First Name</input-label>
`
});
But what if you need to listen for the enter keypress on the input? Naturally, we would like to add the event on the component we added.
import Vue from "https://unpkg.com/vue/dist/vue.esm.browser.js";
const InputLabel = {
template: `
<label>
<span>
<slot></slot>
</span>
<input type="text">
</label>
`
};
new Vue({
el: "#vue",
components: {
InputLabel
},
template: `
<input-label @keydown.enter="keydownEnterHandler">First Name</input-label>
`,
methods: {
keydownEnterHandler() {
alert("enter key pressed!");
}
}
});
Aaaaaaand... That would not work. Because you need to attach the keydown event to the template of the component, and not the component itself. So, for that, you can use the native
modifier.
import Vue from "https://unpkg.com/vue/dist/vue.esm.browser.js";
const InputLabel = {
template: `
<label>
<span>
<slot></slot>
</span>
<input type="text">
</label>
`
};
new Vue({
el: "#vue",
components: {
InputLabel
},
template: `
<input-label @keydown.enter.native="keydownEnterHandler">First Name</input-label>
`,
methods: {
keydownEnterHandler() {
alert("enter key pressed!");
}
}
});
And... Success! This now works as expected. But what if we want to add some custom attributes to our input? This would be great if the native
modifier would work like events, right?
import Vue from "https://unpkg.com/vue/dist/vue.esm.browser.js";
const InputLabel = {
template: `
<label>
<span>
<slot></slot>
</span>
<input type="text">
</label>
`
};
new Vue({
el: "#vue",
components: {
InputLabel
},
template: `
<input-label
@keydown.enter.native="keydownEnterHandler"
value.native="John DOE">
First Name
</input-label>
`,
methods: {
keydownEnterHandler() {
alert("enter key pressed!");
}
}
});
Unfortunately, this won't. Eventually, we would like to not be forced to add that native
each time we want to use our events, since we have no custom events at all. There is a solution for that my friends, actually two solutions which are.
$listeners & $attrs
In each Vue.js components, we have access to two special variables which are $listeners
& $attrs
.
$listeners
will contain all events that are attached to the components that would normally be used. For instance, custom events like @thingyChanged
. Whereas $attrs
will contain all custom attributes that the components will receive like small-column
.
Let's see how to use them in our component.
import Vue from "https://unpkg.com/vue/dist/vue.esm.browser.js";
const InputLabel = {
template: `
<label>
<span>
<slot></slot>
</span>
<input
type="text"
v-on="$listeners"
v-bind="$attrs">
</label>
`
};
new Vue({
el: "#vue",
components: {
InputLabel
},
template: `
<input-label
@keydown.enter="keydownEnterHandler"
value="John DOE">
First Name
</input-label>
`,
methods: {
keydownEnterHandler() {
alert("enter key pressed!");
}
}
});
That's it! We don't need to add the native
modifier anymore to our events, they will work seamlessly. Meaning that all future attributes & listeners will be bound to the <input>
element.
This also means that our value
attribute, which didn't work earlier anyway, now does. Thanks to those magic keywords.
As you may notice, I used a weird alternative syntax to the classic v-on:something="something"
syntax. Actually, you can use v-on
without any arguments. It will receive an object containing your wanted events. In fact, v-on:click="something"
is just syntactic sugar to v-on="{click: something}"
. Same thing goes for v-bind
.
It is also worth mentioning that @click
is equivalent to v-on:click
.
You can now enjoy writing little atomic components that will react the way you expect them to do in your applications.
Why would I need to do that?
Components should be simple and no-brainers. If we wanted to actually use our value
attribute, we would have to define a props
for that matter. Imagine doing that for fifteen, or more attributes & events. This solution does not scale. But using $listeners
& $attrs
does scale, a lot.
Let me know what kind of component refactoring you are intending to do so we can discuss about them in the comment section. Do no hesitate to ask me if you are still struggling to understand some part of that article, I'll be glad to help you understand it.
Thanks for reading and see you next time!
Top comments (0)