Mixins and Plugins in VueJS

There is a tendency to reach for component-first solutions while developing with a library like VueJS. Why wouldn’t there be; after all, it’s a sweet library to segregate functionality and visual behavior into components. But, sometimes, you might need to share functionality across multiple components/Vue instances. That’s where mixins and plugins can provide a lot of help.


Mixins

The origin of the term “mix-in” can be traced back to an ice cream mixture in the 1970s that gave inspiration to not only Cold Stone, but to an object-oriented approach of Lisp. Apparently this caught on in a myriad of programming languages including D, Python, and Ruby. In the fantastic world of front-end web development, we also have Sass that uses mixins in a way that makes styling a lot more fun.

If you’re familiar with inheritance in Java/C#, you might remember that you can only do classical inheritance from one parent class (that’s not to include interfaces). Many Object Oriented Programming languages do not allow for multiple inheritance. As such, one route that some languages use for providing a similar behavior is through mixins.

Mixins allow for you to have methods and properties be applied to several classes. It is also an approach for favoring composition over inheritance, which seems to have become something of a buzzphrase. Within the awesome Vue space, we’ll be talking about components/Vue instances, instead of traditional classes.


A mixin is merely a JavaScript object with functions, props, data, computed properties, that you want to pass along.

Scenario 1: Trevor has a pretty sweet set of components and wants to add a max-height property to them. Based upon the value of this prop, he wants to set the max-height of his component.

//mixin.js
export default {
props:{
maxHeight:{
type:Number,
required:false
}
}
mounted(){
let maxHeight = this.maxHeight;
if(maxHeight){
this.$el.style.maxHeight = maxHeight + 'px';
}
}
}
//sweetComponent.vue
import mixin from 'mixin.js';
export default{
mixins:[mixin]
}

So, now that Trevor has his mixin added to his components, he is free to address other behavior. Even if he adds another mounted function from within the component, both mounted functions will run.

Scenario 2: Brock really needs to a quick way to figure out how many components he has on the page, but he doesn’t have access to Vue Development Tools because his network doesn’t authorize Chrome plugins.

//debugMixin.js
window.components = [];
Vue.mixin({
mounted(){
window.components.push(this);
}
});

Now all Brock has to do to get a quick count is type components.length into his console. By the way, this the way to create a global mixin. The Vue guide warns strongly against this as it impacts every component.

Scenario 3: Jake has several components with repeated functionality. He absolutely loves him some palindromes and really wants to let his users know when the they’ve set the title to a palindrome. Since this doesn’t apply to every component, he needs to target it.

//alertPalindromeMixin.js
export default {
computed:{
isPalindrome(){
return /[^A-Za-z0–9]/g.test(this.title);
}
},
watch:{
isPalindrome(newValue){
if(newValue){
alert("Hey! That's a palindrome!");
}
}
}
}
//someComponent.vue
import palindromeAlertMixin from 'palindromeAlertMixin.js'
export default {
data(){
return{
title:''
}
},
mixins:[palindromeAlertMixin]
}

Now, the users for Jake’s application will know where Jake stands on palindromes. Note that there is a tight coupling in this case. It’s up to you to measure whether or not this is acceptable. In this instance, it probably would have been better to include the title in the mixin. However, it becomes more ambiguous whenever you have complex objects that don’t share the same name.

Out in the wild, you might find some better inspiration:

  1. Vuetify
  2. Vue Material
  3. Element

Plugins

Plugins exist in a large range of software. Notably, we love our plugins for our IDEs. Much like the socket on the left, the term plugin indicates that an interface is provided to allow for extensibility. VueJS allows for such extensibility, and the official plugins, Vuex and Vue-Router are two of the most commonly used plugins. These two plugins give us access to a centralized store and the ability to react to changes in the browser location.

It’s great that plugins like this exist. However, looking at these can make it seem very difficult to create useful plugins for ourselves. This is false. Not only is it super easy to make plugins, they give you the ability to extend Vue components in a useful way that is easily transferable to other projects. If you create something super sweet, you should consider sharing it with the community.

Scenario 4: John would like to use Lodash throughout his application without repeatedly importing it. He really likes the this.$store format that Vuex uses.

//lodashPlugin.js
import _ from 'lodash';
let lodashPlugin = {};
lodashPlugin.install = function (Vue, options) {
    Vue.prototype.$_ = _;
};
//app.js
import Vue from 'vue';
import lodashPlugin from 'lodashPlugin.js';
Vue.use(lodashPlugin);

Now, John is free to call Lodash from his components by using this.$_. Note that the dollar sign is a common convention for plugins in Vue. Technically, you don’t need it, but using it should increase readability if another developer reads your code.

See how painless that was. It’s a very simple use of plugins, but that’s okay! Other common uses for this may include the usage of Axios.

Scenario 5: Jay really loved Angular and had some sweet services that he liked using in his projects. Since moving onto Vue, he wanted to still use them.

//ng-app.js
var app = angular.module('myApp', []);
app.service('StringService', function() {
this.isPalindrome = function (str) {
return /[^A-Za-z0–9]/g.test(str);
};
this.isLengthEven = function (str) {
return str.split('').length % 2 === 0;
};
});

From this service, let’s copy over the anonymous function and make our plugin.

//stringPlugin.js
export default {
install:function(Vue, options){
Vue.prototype.$string = (function(){
return new function(){
this.isPalindrome = function (str) {
return /[^A-Za-z0–9]/g.test(str);
};
this.isLengthEven = function (str) {
return str.split('').length % 2 === 0;
};
}
})();
}
};

While this might seem to be a quick way to copy it over, it’s a bit awkward. It’s better to follow this format:

export default {
install:function(Vue, options){
Vue.prototype.$string = {};
Vue.prototype.$string.isPalindrome = function (str) {
return /[^A-Za-z0–9]/g.test(str);
};
Vue.prototype.$string.isLengthEven = function (str) {
return str.split('').length % 2 === 0;
};
}
};

This is a bit cleaner, and I’m sure that the community has some ideas on even better formats.

Scenario 6: Peyton has some sweet Vue directives that he wants to share with the community. In order to do this, he wants to create a plugin.

//sweetDirectivesPlugin.js
export default {
install:function(Vue, options){
      Vue.directive('inserted',{
inserted:function(el, binding){
if(binding){
binding.value();
}
}
});
};
};

You might find better examples in the wild.

  1. Vue2-Notify
  2. Vue-Charts
  3. Animated Vue

And of course, take time to look at Vue Router and Vuex.

Wrapping Up

Mixins and plugins have the ability to improve readability and portability of your code. Keeping this in mind, using mixins can actually obscure functionality and make your code less readable. As such, you’ll only want to use them when targeting multiple components. It’s up to you to measure the tradeoffs of using mixins/plugins/imports/repeated code.


Bonus

Based on a comment, the question was proposed asking: “If Jake the palindrome-lover wants the palindrome mixin to test things that are not title, dynamically, what is he to do?” Something like the following might work.

//alertPalindromeMixin.js
export default {
computed:{
isPalindrome(){
let palindromeTestField = 'title';
if(this.palindromeTestField){
palindromeTestField = this.palindromeTestField;
}
return /[^A-Za-z0–9]/g.test(this[palindromeTestField]);
}
},
watch:{
isPalindrome(newValue){
if(newValue){
alert("Hey! That's a palindrome!");
}
}
}
}
//someComponent.vue
import palindromeAlertMixin from 'palindromeAlertMixin.js'
export default {
data(){
return{
shortDescription:'mad at a dam',
palindromeTestField:'shortDescription'
}
},
mixins:[palindromeAlertMixin]
}

A word of caution with this approach though. This makes the component a little more coupled to the mixin. That being said, it was already there to begin with.

Thanks for reading!

Denny Headrick loves to work with and write about Vue.js. You can follow him on Twitter at @dennythecoder.

Web Dev and Vue.js are so Damn Fun