Transparent Wrapper Components in Vue
In a current Vue project, I have a couple different components that are almost just native elements, but with a slight wrapper around them that adds functionality. Examples include a date input that uses a particular date picker and a textarea that auto resizes.
For these components, I want them to behave as closely as possible to if they are native components - allowing me to transparently pass through attributes, add event listeners, and more.
Doing this in Vue is completely possible, but takes a bit of doing. Here are the steps I came to, using the autosizing textarea as an example.
Step 1: Implement v-model using :value and @input
According to the Vue Guide on components, v-model on a component essentially works by passing in a
value prop, and applying and
input event handler.
The first step to making our component then feel like a native element is to set these up with a template that looks like:
Now if someone uses v-model with our component, it will behave exactly as if they had applied v-model to the underlying textarea.
Step 2: Pass through other event listeners
We want to make sure input is working, because that is key to making v-model work, but we also want our component to handle other event listeners that we might apply. For example, we might want to apply a listener on focus in some locations, or something else.
Rather than try to think of all possible listeners, we're going to take advantage of the builtin
$listeners object on our component, which lets us get all listeners applied to the component.
and then in the template applying them in bulk:
Step 3: Pass through attributes
As well as event listeners, we also want to pass through any attributes that we might want to go in. By default, Vue will do that for us - all attributes set on a component are passed through to the root element of the component.
However, in this case we want to special case some of those attributes by setting defaults. To do this, we're going to disable this automatic attribute pass-through by setting
inheritAttrs: false, and then use a similar approach to listeners to reapply them after our defaults.
For an example in our autosizing textarea, we want to default rows to be 3, so we can do something like:
and then in the template:
Apply custom functionality
Finally to simply add our autosizing capability, using the autosize npm package, we import autosize and add a quick mounted hook:
At the end of this, our full single file component template looks like this: