Custom Directives

The Basics

Vue.js allows you to register custom directives, essentially enabling you to teach Vue new tricks on how to map data changes to DOM behavior. You can register a global custom directive with the Vue.directive(id, definition) method, passing in a directive id followed by a definition object. A definition object can provide several hook functions (all optional):

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Vue.directive('my-directive', {
bind: function () {
// do preparation work
// e.g. add event listeners or expensive stuff
// that needs to be run only once
},
update: function (newValue, oldValue) {
// do something based on the updated value
// this will also be called for the initial value
},
unbind: function () {
// do clean up work
// e.g. remove event listeners added in bind()
}
})

Once registered, you can use it in Vue.js templates like this (you need to add the Vue.js prefix to it):

1
<div v-my-directive="someValue"></div>

When you only need the update function, you can pass in a single function instead of the definition object:

1
2
3
Vue.directive('my-directive', function (value) {
// this function will be used as update()
})

All the hook functions will be copied into the actual directive object, which you can access inside these functions as their this context. The directive object exposes some useful properties:

You should treat all these properties as read-only and refrain from changing them. You can attach custom properties to the directive object too, but be careful not to accidentally overwrite existing internal ones.

An example of a custom directive using some of these properties:

1
<div id="demo" v-demo="LightSlateGray : msg"></div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Vue.directive('demo', {
bind: function () {
this.el.style.color = '#fff'
this.el.style.backgroundColor = this.arg
},
update: function (value) {
this.el.innerHTML =
'name - ' + this.name + '<br>' +
'raw - ' + this.raw + '<br>' +
'expression - ' + this.expression + '<br>' +
'argument - ' + this.arg + '<br>' +
'value - ' + value
}
})
var demo = new Vue({
el: '#demo',
data: {
msg: 'hello!'
}
})

Result

Multiple Clauses

Comma separated arguments are bound as multiple directive instances. In the following example, directive methods are called twice:

1
<div v-demo="color: 'white', text: 'hello!'"></div>

You can achieve single binding with all arguments by closing value with object literal:

1
<div v-demo="{color: 'white', text: 'hello!'}"></div>
1
2
3
Vue.directive('demo', function (value) {
console.log(value) // Object {color: 'white', text: 'hello!'}
})

Literal Directives

If you pass in isLiteral: true when creating a custom directive, the attribute value will be taken as a literal string and assigned as that directive’s expression. The directive will not attempt to setup data observation.

Example:

1
<div v-literal-dir="foo"></div>
1
2
3
4
5
6
Vue.directive('literal-dir', {
isLiteral: true,
bind: function () {
console.log(this.expression) // 'foo'
}
})

Dynamic Literal

However, in the case that the literal directive contains mustache tags, the behavior is as follows:

Two-way Directives

If your directive expects to write data back to the Vue instance, you need to pass in twoWay: true. This option allows the use of this.set(value) inside the directive:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Vue.directive('example', {
twoWay: true,
bind: function () {
this.handler = function () {
// set data back to the vm.
// If the directive is bound as v-example="a.b.c",
// this will attempt to set `vm.a.b.c` with the
// given value.
this.set(this.el.value)
}.bind(this)
this.el.addEventListener('input', this.handler)
},
unbind: function () {
this.el.removeEventListener('input', this.handler)
}
})

Inline Statements

Passing in acceptStatement:true enables your custom directive to accept inline statements like v-on does:

1
<div v-my-directive="a++"></div>
1
2
3
4
5
6
7
8
Vue.directive('my-directive', {
acceptStatement: true,
update: function (fn) {
// the passed in value is a function which when called,
// will execute the "a++" statement in the owner vm's
// scope.
}
})

Use this wisely though, because in general you want to avoid side-effects in your templates.

Deep Observation

If your custom directive is expected to be used on an Object, and it needs to trigger update when a nested property inside the object changes, you need to pass in deep: true in your directive definition.

1
<div v-my-directive="obj"></div>
1
2
3
4
5
6
7
Vue.directive('my-directive', {
deep: true,
update: function (obj) {
// will be called when nested properties in `obj`
// changes.
}
})

Directive Priority

You can optionally provide a priority number for your directive (defaults to 0). A directive with a higher priority will be processed earlier than other directives on the same element. Directives with the same priority will be processed in the order they appear in the element’s attribute list, although that order is not guaranteed to be consistent in different browsers.

You can checkout the priorities for some built-in directives in the API reference. Additionally, logic control directives v-if and v-repeat are considered “terminal” and they always have the highest priority in the compilation process.

Element Directives

In some cases, we may want our directive to be used in the form of a custom element rather than as an attribute. This is very similar to Angular’s notion of “E” mode directives. Element directives provide a lighter-weight alternative to full-blown components (which are explained later in the guide). You can register a custom element directive like so:

1
2
3
4
5
6
Vue.elementDirective('my-directive', {
// same API as normal directives
bind: function () {
// manipulate this.el...
}
})

Then, instead of:

1
<div v-my-directive></div>

We can write:

1
<my-directive></my-directive>

Element directives cannot accept arguments or expressions, but it can read the element’s attributes to determine its behavior.

A big difference from normal directives is that element directives are terminal, which means once Vue encounters an element directive, it will leave that element and all its children alone - only the element directive itself will be able to manipulate that element and its children.

Next, we’ll see how to write a custom filter.