Vue supports several advanced features that let us do things that other pieces of Vue code can't do.
To manipulate DOM elements in ways that aren't provided by built-in directives, we have to write our own directives.
If we want to format our output with reusable code, then we have to define our own filters. Filters are just functions that can be applied to templates.
Mixins are code that has reusable parts of a Vue component. We can add merge mixins into our Vue component code so that we can have one piece of code that's shared between multiple components.
In this article, we'll look at how to define and use Vue directives, filters, and mixins in our Vue apps.
Directives
Vue directives are reusable pieces of code that let us manipulate DOM elements in our apps.
There are built-in ones like v-model
to bind our input data into component model properties, and v-show
which lets us show and hide elements with CSS based on some conditions.
The built-in Vue directives can't do everything - so, if we want directives with custom functionality, we have to create them ourselves.
We can define a global directive with the Vue.directive
method with the directive name string as the first argument. The second argument is an object that has the directive hooks methods.
A directive can have the following hooks:
-
bind
- this is called only once: when the directive is first bound to the element. We can run the setup code that only runs once in this function. -
inserted
- this is called when the bound element has been inserted into its parent node. The parent node is guaranteed to be present but it's not necessarily in the document. -
update
- this is called after the containing components VNode has been updated but its children may not necessarily have been updated. The directive's value may or may not have changed. -
componentUpdated
- this is called when the component's VNode and the VNode of its children have been updated -
unbind
- this is called only once when the directive is unbound from the element.
For instance, we can define a simple app-wide directive as follows:
index.js
Vue.directive("highlight", {
inserted(el) {
el.style.color = "red";
}
});
new Vue({
el: "#app"
});
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>App</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<p v-highlight>foo</p>
</div>
<script src="index.js"></script>
</body>
</html>
In the code above, we defined the directive called 'highlight'
that has the inserted hook, which we used to change the color of the content that's bound to this directive. We changed the color of the content to red when the VNode is inserted into the virtual DOM tree with:
el.style.color = "red";
The el
above is the DOM element that the directive is bound to. Since DOM element objects have the style
property (which has the color
property), we can set it to set the color of the content of the DOM element.
el
is used to manipulate the DOM directly.
Then, in index.html
, we added the highlight
directive into our template by writing v-highlight
. Vue knows that anything with a v-
prefix in the opening tag of an element is a directive. It'll look for the directive with the name without the v-
prefix.
Therefore, the p
element in index.html
will have the red color applied to it.
We don't have to define and register directives globally, which makes them available for the whole app. We can also define directives that are only available within a component by adding a directive
property into our component.
For instance, we can define a directive as follows:
index.js
new Vue({
el: "#app",
directives: {
highlight: {
inserted(el) {
el.style.color = "red";
}
}
}
});
In the code above, we added the directives
property to the Vue instance. Inside it, we added the highlight
property so that we can add the same highlight
directive as we did before.
Then, we can use them the same way we did before.
Directive Hook Arguments
Hooks take multiple arguments. As we can see from the examples above, the el
parameter is the first argument of the hook.
The second argument is binding
, which is an object containing the following properties:
-
name
- the name of the directive without thev-
prefix -
value
- the value passed to the directive. For instance, if we havev-directive:foo='1'
thenvalue
is 1 -
oldValue
- the value previously passed to the directive; it's only available in theupdated
andcomponentUpdated
hooks. It's available regardless of whether the value has changed -
expression
- the expression of the binding as a string. For instance, if we havev-directive:foo='1 + 2'
thenexpression
is'1 + 2'
-
arg
- the argument passed into the directive. For instance, inv-directive:foo
,foo
is the value ofarg
-
modifiers
- an object containing modifiers. For instance, if we havev-directive.bar.baz
then themodifiers
object value is{ bar: true, baz: true }
The 3rd argument is vnode
which is the virtual node object produced by Vue's compiler.
The last argument is oldVnode
, which is the previous virtual node and it's only updated in the update
and componentUpdated
hooks.
All arguments other than el
are read-only.
For instance, we can use them as follows:
index.js
Vue.directive("padding", {
bind(el, binding) {
const { value } = binding;
const { top, left, bottom, right } = value;
el.style.padding = `${top || 0}px ${right || 0}px ${bottom || 0}px ${left ||
0}px`;
}
});
new Vue({
el: "#app"
});
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>App</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<p v-padding="{ top: 30, bottom: 30, left: 20, right: 20 }">
foo
</p>
</div>
<script src="index.js"></script>
</body>
</html>
In the code above, we have a padding
that takes an object as its value and gets that object via the binding
parameter of the bind
method of the directive. Then, it takes the destructured values from the object that is passed into the directive.
We used those values to set the padding on each side of the p element.
Dynamic Directive Arguments
We can have dynamic arguments in a directive. To add them into a directive, we can use the bracket notation in our template as follows:
index.js
Vue.directive("top-position", {
bind(el, binding, vnode) {
const { value, arg } = binding;
el.style.position = arg;
el.style.top = `${value}px`;
}
});
new Vue({
el: "#app",
data: {
position: "fixed"
}
});
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>App</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<p v-top-position:[position]="200">foo</p>
</div>
<script src="index.js"></script>
</body>
</html>
In the code above, we defined the top-position
directive, which takes a number as a value and a dynamic position
argument, which we've set as 'fixed'
in the data
property of the Vue
instance.
Therefore, the content of the p
element will be moved 200px down from its regular position.
Function Shorthand
If we only want the same behavior as on the bind
and update
, we can pass in a function as the 2nd argument of the Vue.directive
method as follows:
Vue.directive("top-position", (el, binding, vnode) => {
const { value, arg } = binding;
el.style.position = arg;
el.style.top = `${value}px`;
});
new Vue({
el: "#app"
});
The code above does the same thing as our previous example. The only difference is that it's shorter.
Filters
Filters let us format data that we displayed on templates. They can be used in template interpolation and as expressions in v-bind
.
We can define filters globally with the Vue.filter
method as follows:
index.js
Vue.filter("localeString", function(value) {
if (value instanceof Date) {
return value.toLocaleDateString();
}
return value;
});
new Vue({
el: "#app"
});
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>App</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
{{new Date() | localeString}}
</div>
<script src="index.js"></script>
</body>
</html>
In the code above, we defined the localeString
filter. It's defined by the Vue.filter
method with the filter name string passed in as the first argument and a function to transform the value into something we want to display as the second argument.
Then, in index.html
, we used the localeString
filter as we did in the div
. Filters are applied by using the |
symbol.
Therefore, we should get the current date as a formatted date string.
We can also define filters within a component as follows:
new Vue({
el: "#app",
filters: {
localeString(value) {
if (value instanceof Date) {
return value.toLocaleDateString();
}
return value;
}
}
});
In the code above, we have the filter
property in our Vue instance. Inside it, we have the localeString
function, which is our filter function.
It does the same thing as the global version we defined above, except that it only works within the component.
Filters can also be chained as follows:
{{ message | capitalize | format }}
As a result, the capitalize
and format
filters are invoked one after the other.
Filters can also take an argument with an arg
parameter as shown below:
new Vue({
el: "#app",
filters: {
multiply(value, arg) {
if (typeof value === "number") {
return value * arg;
}
return value;
}
}
});
Then, we can use it as follows:
{{1 | multiply(2)}}
As a result, we see the number 2 displayed since 2 is passed into the multiply
filter.
Mixins
Mixins are reusable pieces of code that can be incorporated into multiple components.
A mixin is just an object with the regular properties of a Vue component like methods
and hooks like the created
hook.
For instance, we can create a mixin and use it as follows:
const helloMixin = {
created() {
this.hello();
},
methods: {
hello() {
alert("hello");
}
}
};
new Vue({
el: "#app",
mixins: [helloMixin]
});
In the code above, we defined a mixin called helloMixin
, which has the created
hook. This hook calls the hello
method defined in the methods
property of a mixin.
Mixin hooks are merged into an array so that all of them will be called.
It’s worth it to note that mixin hooks are called before the component's own hooks.
Property hooks that have object values like methods
, components
and directives
will be merged into one object.
A plain object mixin has to be explicitly incorporated into a component. However, we can also define a global mixin with the Vue.mixin
method as follows:
Vue.mixin({
created() {
this.hello();
},
methods: {
hello() {
alert("hello");
}
}
});
new Vue({
el: "#app"
});
In the code above, we defined a mixin with the Vue.mixin
method, which incorporates the mixin automatically into our Vue instance without writing any code to do so.
Therefore, we should use this carefully since it affects all components in our app.
Conclusion
Directives are useful for manipulating the DOM. They take modifiers, expressions that are evaluated, and arguments to customize how a directive acts.
It can take various lifecycle hooks to let us run code when the VNode is added, when it updates, or when it uploads.
Filters are code that let us format our template data the way we wish. They can be chained and they also take arguments.
Mixins are reusable pieces of code that can be incorporated into components. They can either be defined globally, which automatically incorporates them into our components, or defined as an object, which has to be explicitly incorporated.
As a final word, if you're developing commercial or enterprise Vue apps, make sure you are protecting their code against reverse-engineering, abuse, and tampering by following this tutorial.