Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Sharing Vue-ish Logic: Mixin Basics

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

We've talked quite a bit about code re-use in Vue. And there are few types we know about. For data related stuff, like Ajax calls, we have a nice services/ directory full of modules that return functions. For other logic, like formatting a price, we have a helpers/ directory. And most importantly, to isolate chunks of markup and the behavior for that markup, we can create components. One of the nicest examples we have is color-selector: it's some markup along with behavior that let's us choose a color & emits an event.

But there's another type of functionality that you sometimes need to share. It's, sort of, Vue-related behavior that is not specific to any one component and markup.

Why Mixins?

Consider the product page inside of components/product-show.vue. This makes an AJAX call for the cart down in created, and allows us to add an item to the cart with a few extra benefits: like tracking if it's still saving and when it's successful. It even updates the header with the new cart item count!

Well... it turns out that our new shopping cart page will also need to make an AJAX call for the cart data... and it will also need the ability to add an item to the cart. Well, not exactly. We're going to allow users to change the quantity of each item, which is similar to adding an item. In both cases, we may want to show a little loading animation when it's saving and update the header when it's done.

The point: these two components need to share a bunch of behavior, like the logic in addToCart and even pieces of data... but with totally different HTML. That means we can't just isolate this stuff into its own component.

What's the solution? Say hello to mixins. Very simply mixins allow us to extract parts of a Vue components - like data, methods or even things like a created function - into something that can be reused by many components. In Vue 3, mixins still exist, but are replaced by the composition API - something we'll talk about in a future tutorial. But fundamentally, both mixins and composition do the same thing, just with a slightly different mechanism.

Bootstrapping the Mixing

Let's start really simple: with a mixin that holds a cart data and fetches that via AJAX in a created function. In assets/, create a new directory called mixins/ and, inside, a new file called get-shopping-cart.js... because that's kind of the point of this file: to get the shopping cart data.

Mixins mostly look... just like a component! We export default an object. And inside, we can have most of the same keys as a component. Start with a data key holding cart set to null.

... lines 1 - 2
export default {
data() {
return {
cart: null,
};
},
... lines 9 - 13
};

Next, back in the component, copy the created function, paste it into the mixin... then delete the part that fetches the product.

import { fetchCart } from '@/services/cart-service';
export default {
... lines 4 - 8
async created() {
fetchCart().then((cart) => {
this.cart = cart;
});
},
};

Ok, that's enough to start! Oh, but now that this is the only AJAX call in created, we can simplify with await: this.cart = await fetchCart().

import { fetchCart } from '@/services/cart-service';
export default {
... lines 4 - 8
async created() {
this.cart = await fetchCart();
},
};

Cool! Say hello to our simple mixin! We can now use this inside any component and that component will magically have a cart data and a created function as if this code were literally written inside that component.

Using a Mixin

So... let's go use this! In product-show.vue, find the import section and import the mixin like any normal variable: import ShoppingCartMixin from '@/mixins/get-shopping-cart':

... lines 1 - 75
<script>
... lines 77 - 82
import ShoppingCartMixin from '@/mixins/get-shopping-cart';
... lines 84 - 150
</script>
... lines 152 - 169

Then, after the components option, add a special new key: mixins. This is simple: set it to an array with ShoppingCartMixin inside.

... lines 1 - 75
<script>
... lines 77 - 82
import ShoppingCartMixin from '@/mixins/get-shopping-cart';
export default {
name: 'ProductShow',
... lines 87 - 91
mixins: [ShoppingCartMixin],
... lines 93 - 149
};
</script>
... lines 152 - 169

Thanks to this, we do not need the cart data anymore. It will already exist thanks to the mixin. By the way, as powerful as mixins are, this is one of its biggest downsides: for our human brains... and also for my editor, it's not super clear that there is a cart data or where it's coming from. Mixins are truly Vue magic.

We also don't need the fetchCart logic down in created. Both the created function from our component and the mixin will be called. I'll even remove the extra import on top to keep things clean.

Ok! With any luck, this will work exactly like before. Let's try it! Go back to the homepage, click into a product and... let's add 3 more of these to our cart. Watch that 12 in the header... and hit "Add to Cart". Got it!

Next, there is a lot more cart functionality that we can move into the mixin, like the logic to add an item to the cart - including the loading state - and updating the cart header. That will make this a truly valuable mixin when we build the shopping cart page.

Leave a comment!

0
Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

This course is also built to work with Vue 3!

What JavaScript libraries does this tutorial use?

// package.json
{
    "devDependencies": {
        "@fortawesome/fontawesome-free": "^5.15.1", // 5.15.1
        "@symfony/webpack-encore": "^0.30.0", // 0.30.2
        "axios": "^0.19.2", // 0.19.2
        "bootstrap": "^4.4.1", // 4.5.3
        "core-js": "^3.0.0", // 3.6.5
        "eslint": "^6.7.2", // 6.8.0
        "eslint-config-airbnb-base": "^14.0.0", // 14.2.0
        "eslint-plugin-import": "^2.19.1", // 2.22.1
        "eslint-plugin-vue": "^6.0.1", // 6.2.2
        "regenerator-runtime": "^0.13.2", // 0.13.7
        "sass": "^1.29.0", // 1.29.0
        "sass-loader": "^8.0.0", // 8.0.2
        "vue": "^2.6.11", // 2.6.12
        "vue-loader": "^15.9.1", // 15.9.4
        "vue-template-compiler": "^2.6.11", // 2.6.12
        "webpack-notifier": "^1.6.0" // 1.8.0
    }
}
userVoice