Custom Directives in Vue JS
Tags: vue
Published: 2020-09-17 13:12:37

---
title: Custom Directives in Vue JS
published: true
date: 2020-07-13 21:35:08 UTC
tags: vue
canonical_url: https://www.thisdot.co/blog/custom-directives-in-vue-js
---

Vue JS promotes the use of components as the primary form of code reuse and abstraction. However, there are some instances when you want to manipulate the low-level DOM from within Vue JS components. In these instances, directives come to the rescue!

If you have already been developing apps with Vue JS you must surely be familiar with some of the core directives offered by the Vue JS core team. Here are a few worth mentioning: `v-model`, `v-if`, `v-for`’, etc.

In this article, I will cover everything you need to know to start building your own custom directives in Vue JS.

## Dissect a Custom Directive

Custom directives enrich HTML with new reactive functionality that’s fully managed by Vue JS.

Let’s start with a full dissection of a basic custom directive written for Vue JS.

Consider the following directive:

```
Vue.directive('focus', {
  // When the bound element is inserted into the DOM...
  inserted: function (el) {
    // Focus the element
    el.focus()
  }
})
```

  
This example registers a new global custom directive into the main Vue instance. Later I will discuss the different ways available for registering directives. For now, let’s focus on the directive itself.

A custom directive is defined by a JavaScript literal object implementing a set of functions. These functions are called hooks by Vue JS and are standard to any custom directive. More on hook functions in the coming section.

The `inserted()` hook function accepts the `el` input parameter. This parameter represents the HTML element where this custom directive is applied.

Inside the function, the `focus()` function is called on the element itself.

In summary, when the element with the custom directive is added to its parent node, this function runs and makes the element in the focus state.

How do you apply this custom directive inside a component? Every custom directive should be prefixed by the letter `v-`. In this case, assuming we are adding this custom directive to an input control, then it follows like this:

```
<input v-focus>
```

### Hook Functions

All hook functions provided by Vue Js for building custom directives are optional. Hook functions are there to help you customize and provide the needed functionality for the directive at certain stages of the directive life cycle.

There are five available:

- bind
- inserted
- update
- componentUpdate
- unbind

#### bind

This function is called once when the directive is bound to the underlying element. Think of it as a **one-time setup** hook.

#### inserted

This is called when the underlying element is inserted into the parent node. This doesn’t mean the element is inserted into the live DOM but rather its context is now known and part of a well-defined tree of nodes. You can read more about [VNodes](https://vuejs.org/v2/guide/render-function.html) to understand how Vue JS works with them.

#### update

This function is called after the containing component’s VNode has updated, but possibly before its children have updated.

#### componentUpdate

This is called after the containing component’s VNode and the VNodes of its children have updated.

##### unbind

This function is called only once when the directive is unbound from the element.

Vue JS engine passes the same set of input parameters to all hook functions. Let’s look at these parameters.

### Binding Function Parameters

Each and every hook function receives the same set of input parameters defined as follows.

#### el

This parameter represents the element that this custom directive is applied to. It can be any valid HTML element.

#### binding

This input parameter is an object containing the following properties:

**name:** The name of the directive without the `v-` prefix. For instance, using a custom directive as `v-focus` yields a name of `focus`.

**value:** The value passed to the directive. For instance, using the `v-slot=”prop”` directive yields a value of `prop`.

**oldValue:** This field is only available inside `update()` and `componentUpdate()` hook functions. It contains the previous value of the directive, before the update.

**expression:** This field represents the expression of the binding as a string literal. For instance, using the custom directive `v-add=”1+1”` yields an expression of `”1+1”`.

**arg:** This field represents the argument (if any) that’s passed to the directive. There can be only one argument passed. For instance, using the `v-slot:default` directive yields an argument of `default`.

**modifiers:** This field is an object containing modifiers that could change and control the behavior of the directive if they are set. Think of modifiers as flags you set on the directive. If a modifier is set, it will have a value of `true`, if not set, it won’t even be visible to the directive. For example, using the directive `v-on:click.prevent` yields a modifier of `{ prevent: true }` object.

#### vnode

The virtual node produced by Vue’s compiler. See the [VNode API](https://vuejs.org/v2/api/#VNode-Interface) for full details.

#### oldVnode

The previous virtual node, only available in the `update()` and `componentUpdated()` hooks.

Now that you know all about hooks and the details about their input parameters, let’s see how you register a custom directive in your Vue JS app.

### Globally Registered Directives

There are two ways to define and register a custom directive. In this section, we will look at how to register a custom directive globally in your app.

To do this, navigate to the **main.js** file located at the root folder of your application and add the following to register the `focus` custom directive.

```
import Vue from "vue";
import App from "./App.vue";

Vue.config.productionTip = false;

// Register a global custom directive called `v-focus`
Vue.directive('focus', {
  // When the bound element is inserted into the DOM...
  inserted: function (el) {
    // Focus the element
    el.focus()
  }
})

new Vue({
  render: h => h(App)
}).$mount("#app");
```

  
The `Vue.directive()` function accepts as a first parameter the name of the custom directive (without the `v-` prefix). The second parameter is the custom directive object. In this case, the object contains the `inserted()` hook function only.

That’s it! Now you can use the custom directive anywhere inside your components.

### Locally Registered Directives

The second way of registering custom directives is local to the component. You can define and register a custom directive to be used inside a single component. In case you want to use the same custom directive somewhere else in your app, you have to redefine it again inside the other component.

This method of registering custom directives is definitely limited and might not be used often, if not at all!

I strongly recommend registering your custom directives as global directives for better performance and easier access across your app.

To register a custom directive locally, navigate to the component where you want to use the custom directive, and add the method below as part of the [Vue Options API](https://012.vuejs.org/api/options.html):

```
…
directives: {
  focus: {
    // directive definition
    inserted: function (el) {
      el.focus()
    }
  }
}
...
```

  
That’s it!
## Demo: List Custom Directive

Now that you understand custom directives in Vue JS, let’s build a custom directive.

The custom directive I am going to build in this section is the `v-list` directive.

Using this custom directive as such:

```
<div class="list" v-list:ul.square.inside="items"/>
```

  
Yields the following HTML being generated inside the DOM:

```
<ul style="list-style-type: square; list-style-position: inside;">
   <li>Item #1</li>
   <li>Item #2</li>
   <li>Item #3</li>
</ul>
```

  
Given a variable named `items` defined as an array of strings, yields the app showing in \_\_Figure 1\_\_:

| ![figure1](http://images.ctfassets.net/zojzzdop0fzx/5PWQHQTQPsp4J0chEFKt0S/e53a3abd3cb1fd765b93b718c67d6829/figure1.PNG) |
| --- |
| **Figure 1** : The List custom directive in action |

**Figure 2** below shows the details of using this custom directive:

| ![figure2](http://images.ctfassets.net/zojzzdop0fzx/4KqhuFeoCskQs88Qef6Xng/d9f6c1ace9cdf9510e8de6ce790a665e/figure2.PNG) |
| --- |
| **Figure 2** : Using the custom directive |

The diagram above is self-explanatory!

Let’s sift through the code and define this custom directive.

> You can play with the custom directive on [codesandbox.io](https://codesandbox.io/s/v-list-custom-directive-in-vuejs-2-o2wet).

Add a new **\directives\List.directive.js** file and add the following code:

```
const directive = {
  bind(el, binding, vnode) {
    if (!Array.isArray(binding.value)) {
      return;
    }

    // validate value, arguments, and modifiers
    const { items, listType, listStyleType, listStylePosition } = validate(
      binding
    );

    render(el, { items, listType, listStyleType, listStylePosition });
  }
};

export default directive;
```

  
This code snippet defines an object called `directive`. Then, this object is exported as the default export of this code file.

The custom directive at hand makes use of the `bind()` hook function to implement the functionality of this directive.

First of all, it checks if the `binding.value` is bound to an array variable. If not, it returns and nothing happens.

The next step is to validate the argument and modifiers. This is done in a separate local utility function called `validate`. We will get into this very shortly.

The `validate()` function not only validates the different parts of the custom directive but also sets some default values in order to appease the rendering process.

Finally, it’s time to render the list, whether a `ul` or `ol` list.

Let’s have a look at the `validate()` method.

```
const validate = binding => {
  let results = {
    items: [],
    listType: "ul",
    listStyleType: "disc",
    listStylePosition: "outside"
  };

  // grab items
  results["items"] = [...binding.value];

  // grab argument
  const arg = binding.arg;
  const validArgs = ["ul", "ol"];

  if (arg && validArgs.includes(arg)) {
    results["listType"] = arg;
  }

  // grab modifiers
  const modifiers = binding.modifiers;
  const validModifiers = [
    "disc",
    "circle",
    "square",
    "decimal",
    "decimal-leading-zero",
    "lower-roman",
    "upper-roman",
    "lower-greek",
    "lower-latin",
    "upper-latin",
    "armenian",
    "georgian",
    "lower-alpha",
    "upper-alpha",
    "none",
    "inside",
    "outside"
  ];

  if (modifiers) {
    for (const [key, value] of Object.entries(modifiers)) {
      if (value) {
        // modifier included
        if (key && validModifiers.includes(key)) {
          if (key === "inside" || key === "outside") {
            results["listStylePosition"] = key;
          } else {
            results["listStyleType"] = key;
          }
        }
      }
    }
  }

  return results;
};
```

  
The method prepares a well-defined result object containing the following properties:
- **`items`** : This property represents the `binding.value` of the directive. Whatever array variable you bind to the directive, it is being captured inside the `items` property.
- **`listType`** : This property represents the type of list to render. Whether it is a `ul` element or `ol` element. It represents the `binding.arg` property defined on the custom directive.
- **`listStyleType`** : This property represents the **list-style-type** CSS property defined on an `ul` or `ol` element. It represents one of the modifiers that this custom directive accepts. The code validates this modifier based on a known list of values that the **list-style-type** property accepts.
- **`listStylePosition`** : This property represents the **list-style-position** CSS property defined on a `ul` or `ol` element. It represents one of the modifiers that this custom directive accepts. The code validates this modifier based on a known list of values that the **list-style-position** property accepts.

The properties above are defined with a default value representing the real default value behind **list-style-type** and **list-style-position** respectively. If these modifiers are incorrect, the default values take precedence.

Let’s have a look at the `render()` method:

```
const render = (el, { items, listType, listStyleType, listStylePosition }) => {
  if (!el) return;

  // clear container
  el.innerHTML = "";

  // add the list
  const list = document.createElement(listType);
  el.appendChild(list);

  // configure list
  list.style.listStyleType = listStyleType;
  list.style.listStylePosition = listStylePosition;

  items.forEach(item => {
    const li = document.createElement("li");

    list.appendChild(li);

    li.textContent = item;
  });
};
```
<br>
This method starts by clearing the parent container, the `el` element.

It then creates a new HTML element, whether a new `ul` or `ol` element. It appends the list into the parent container `el` element.

After that, it sets the `listStyleType` and `listStylePosition` properties on the newly created list element.

It then iterates over the items stored inside the `binding.value` array. For each array item, it creates a new `li` element, appends it to the list element created above, and sets it’s `textContent` property to the value of the array item.

To use this custom directive, switch back to the __\main.js__ file and register this directive globally as follows:

```
...
import ListDirective from "./directives/List.directive";

Vue.directive("list", ListDirective);
...
```
<br>
That’s all!

Navigate to __App.vue__ file and add the directive as follows:

```
<template>
  <div id="app">
    <h2>Awesome List</h2>
    <div class="list" v-list:ul.square.inside="items"/>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      items: ["Item #1", "Item #2", "Item #3"]
    };
  }
};
</script>
```
<br>
Attach the directive to a `<div>` element. Then set the `items` variable to an array of strings. 

Running the app yields the same app shown above in __Figure 1__.

This custom directive can be made much more complicated. However, I opted for a simplified implementation to illustrate the ideas behind building a custom directive in Vue JS.

> You can play with the custom directive on [codesandbox.io](https://codesandbox.io/s/v-list-custom-directive-in-vuejs-2-o2wet).

## Conclusion
Despite the fact that Vue JS pushes for coding components rather than custom directives, there are some instances when you need to manipulate the DOM reactively using custom directives. 
```