Finishing the Cart Controls Component

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 successfully extracted most of the markup and functionality for the "add to cart" controls into this new component. Our template still references a few undefined methods, but thanks to the props and data we added to the component, all the variables should now be defined. And that means, we should be able to get this to render.

Using the Component

Back in index.vue, let's import this puppy: import CartAddControls from and I'll be lazy and just use ./cart-add-controls because they live in the same directory. Add this to components...

... lines 1 - 53
<script>
... lines 55 - 59
import CartAddControls from './cart-add-controls';
... line 61
export default {
name: 'ProductShow',
components: {
... lines 65 - 66
CartAddControls,
},
... lines 69 - 106
};
</script>
... lines 109 - 122

Then head up to the template: <cart-add-controls. Pass this the props it needs: :product set to product and then allowAddToCart. This should be true if the cart is done loading.

As a reminder, this component uses the get-shopping-cart mixin, which means it has a data called cart. We can use that here: allowAddToCart if cart !== null.

<template>
<div>
... lines 3 - 8
<div
... lines 10 - 12
>
... lines 14 - 30
<div class="col-8 p-3">
... lines 32 - 33
<div class="row mt-4 align-items-center">
... lines 35 - 38
<div class="col-8 p-3">
<cart-add-controls
... line 41
:product="product"
:allow-add-to-cart="cart !== null"
... lines 44 - 45
/>
</div>
</div>
</div>
</div>
</div>
</template>
... lines 53 - 122

Next add :add-to-cart-loading set to the addToCartLoading data - which also comes from the mixin - and :add-to-cart-success set to addToCartSuccess.

<template>
<div>
... lines 3 - 8
<div
... lines 10 - 12
>
... lines 14 - 30
<div class="col-8 p-3">
... lines 32 - 33
<div class="row mt-4 align-items-center">
... lines 35 - 38
<div class="col-8 p-3">
<cart-add-controls
... line 41
:product="product"
:allow-add-to-cart="cart !== null"
:add-to-cart-loading="addToCartLoading"
:add-to-cart-success="addToCartSuccess"
/>
</div>
</div>
</div>
</div>
</div>
</template>
... lines 53 - 122

Done! Let's try it out! Move over to the browser and... yes! It renders! Well, yes, it does render, but Vue is totally mad because our template references some undefined methods. Time to fix those!

Moving the Method

The first is updateSelectedColor. Go into the original component and find this method. This updates this.selectedColorId... which no longer lives in this component anyways. Copy the method, delete it, go to cart-add-controls, scroll down, add a new methods section and paste.

... lines 1 - 32
<script>
... lines 34 - 35
export default {
name: 'ProductCartAddControls',
... lines 38 - 64
methods: {
updateSelectedColor(iri) {
this.selectedColorId = iri;
},
},
};
</script>
... lines 72 - 80

Thanks to this, when the color-selector component emits its color-selected event, we call this method and it updates the selectedColorId data.

Emitting an Event

The last undefined method we're referencing is addToCart(), which also lives in the parent index.vue component. This one is a bit more complex. We can't just copy the method and move it... because it calls this.addProductToCart(). If I hold Command or Ctrl and click that method, it comes from the mixin.

And, more importantly, adding an item to the cart modifies the cart data... and so that logic should happen inside the component that holds that data, which is index.vue.

In other words, this is another classic situation where we need to emit an event from a child component so that its parent component can listen to that event and update some data.

And... that's actually awesome! Emitting an event from a component is a great way to make that component generic and reusable. Anyone using this will be able to do whatever logic they want when the "add to cart" button is pressed.

Down in methods, add addToCart(). But now, instead of modifying some data, say this.$emit() to emit an add-to-cart event. Pass this the two things that our component needs to communicate: quantity set to this.quantity and selectedColorId set to this.selectedColorId.

... lines 1 - 32
<script>
... lines 34 - 35
export default {
name: 'ProductCartAddControls',
... lines 38 - 64
methods: {
... lines 66 - 68
addToCart() {
this.$emit('add-to-cart', {
quantity: this.quantity,
selectedColorId: this.selectedColorId,
});
},
},
};
</script>
... lines 78 - 86

Finally, back in index.vue, when we include cart-add-controls, we can listen to this: @add-to-cart= and then call the addToCart() method that we already have... we'll just need to tweak it slightly.

<template>
<div>
... lines 3 - 8
<div
... lines 10 - 12
>
... lines 14 - 30
<div class="col-8 p-3">
... lines 32 - 33
<div class="row mt-4 align-items-center">
... lines 35 - 38
<div class="col-8 p-3">
<cart-add-controls
... lines 41 - 45
@add-to-cart="addToCart"
/>
</div>
</div>
</div>
</div>
</div>
</template>
... lines 54 - 119

Head down to find addToCart(). This will now receive the event object that we're sending to the add-to-cart event: with quantity and selectedColorId. Instead of adding an event argument and reading those keys off of it, let's put our fancy hat on and use object destructuring: {} then quantity and selectedColorId. Remove the this from both of those variables below.

... lines 1 - 54
<script>
... lines 56 - 62
export default {
name: 'ProductShow',
... lines 65 - 98
methods: {
addToCart({ quantity, selectedColorId }) {
this.addProductToCart(this.product, selectedColorId, quantity);
},
},
};
</script>
... lines 106 - 119

Phew! We moved a lot of stuff around to get this working... but we now have a beautiful component that receives the props it needs, manages the data it needs and emits an event when it needs to.

So let's try it! Move over and do a full refresh just in case. We have 12 items in the cart right now. Add 3 blue couches and... yea! The loading animation was right, the check box shows and the cart header updated! And when we go to the cart page, it's there!

Next: let's leverage our hard work to add the featured product sidebar.

Leave a comment!

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
    }
}