April 13, 2020

Advanced Vue Features: Directives, Filters, and Mixins

By John Au-Yeung | 9 min read

Advanced Vue Features: Directives, Filters, and Mixins

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 the v- prefix
  • value - the value passed to the directive. For instance, if we have v-directive:foo='1' then value is 1
  • oldValue - the value previously passed to the directive; it's only available in the updated and componentUpdated hooks. It's available regardless of whether the value has changed
  • expression - the expression of the binding as a string. For instance, if we have v-directive:foo='1 + 2' then expression is '1 + 2'
  • arg - the argument passed into the directive. For instance, in v-directive:foo, foo is the value of arg
  • modifiers - an object containing modifiers. For instance, if we have v-directive.bar.baz then the modifiers 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.

Protect your Vue App with Jscrambler

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 our tutorial.

John Au-YeungWeb developer. Experienced in JavaScript, React, Angular, Vue.js, and Node.js.
View All Posts

Subscribe to our weekly newsletter

Learn more about new security threats and technologies.

I agree to receive these emails and accept the Privacy Policy.