Building on Online Store with Vue CLI — Part 3

Routers and getters and mixins, oh my!

Nathan Magyar
6 min readJan 10, 2019

Part 1 | Part 2 | Part 3:

Previously we were able to get some dummy data to display nicely on the homepage. In this phase, we’ll create a single file component to display information about an individual product.

Step 1: Create a new dynamic route

Vue Router is the plugin we will use to achieve the illusion of different pages in our single page application. Routes, or relative URL paths, get mapped to specific components. So when a user arrives at a certain URL, Vue knows what to render on the screen.

Open router.js and you’ll see that Vue CLI has already done the work of setting up Vue Router. At the top Vue automatically installs Vue Router with the Vue.use() global method. Then it creates a new Router instance with two default routes stored in the “routes” option, one for “home” and one for “about”. As you can see, each route has a path (URL), a name (used when referencing the route elsewhere in the app so you don’t have to remember the path), and the name of the component to be rendered (don’t forget to import any new component at the top before you use it 😉).

Let’s add a new route for the product detail page:

In the above code, we did 4 things:

  1. Imported the Product component (line 4)
  2. Created a dynamic path (line 16). What makes it dynamic? The “:id” part. In a previous post I pointed out that anytime we want to use a piece of data and not a raw string, we have to use the colon, “:id” not “id”. Here, what will happen is “:id” will get replaced by the actual id of the product we click on.
  3. Line 17 specifies the route name
  4. Line 18 maps the route to the component we just imported

Step 2: Create a Product view

In router.js, we pointed our new route to a Product component…but it doesn’t exist yet. Let’s make it now. In the “views” directory, make a new file called Product.vue and add the following:

What’s happening here?

In the template, all we’re displaying is the product name for now and preserving some of the page margin styling. In the script section, we’ve named the component (a best practice), and added a data object that stores the component’s data; to start, it will have one property called product, the specific product we want to display.

Getters

We access the correct product with a getter, a function that gives us direct access to the current state of our application. Here, the getter is also called product and it accepts one argument, the id of the product we want to display. How do we get the id? Through the route! Using

this.$route.params.id

Now that we know what we want the getter to do, we have to define it in store.js, since getters live alongside actions and mutations.

export default new Vuex.Store({
state: {
...
},
mutations: {},
actions: {},
getters: {
product: (state) => (id) => {
return state.products.filter(p => p.id === Number(id))[0]
}
}
});

The first argument for any getter is always state. This above getter though is an example of method-style access, meaning we’re passing it an extra argument, the product id. To do that, we have to return another function, which is the thing that actually gives us the product. We get the product we want by filtering our products array on id match. The id we’ve been passing around gets turned into a string in the process, so in our return statement we have to convert it back into a number:

(id) => {
return state.products.filter(p => p.id === Number(id))[0]
}

Finally, in Home.vue, make each product link to its respective detail page by wrapping the <li> contents in <router-link></router-link> (lines 7 and 11); Webpack will later convert this element into a standard anchor tag (<a></a>). Note <router-link>’s “:to” property, which stores an object with properties of “name” and “params.” As the value of the “params” key, we add another object with a key of “id”, the name of the dynamic property we added in router.js, and the value we want to be passed in when that property is used, the product id.

:to="{ name: 'product', params: { id: product.id}}"

After all this, your homepage should look like this:

Expected output for the homepage with router-links added

And when you click on the Crewneck T-Shirt item you should see:

Expected output for the product detail view page

Step 3: Flesh out the Product view

Next we’ll just add some basic HTML and CSS to nicely display more information about an individual product (image, price, size, color, details):

We can use the same imagePath method that we did before to render the product image in Home.vue.

Note one new directive we’re using above on line 17, v-if, which conditionally shows or hides the element it’s used on if the condition it evaluates is true or false, respectively. In some cases, products have an empty string as the value for the “additional” key under “details,” so I chose to hide that particular <li> whenever that was the case. Since empty strings evaluate to false, I can simply use

product.details.additional

as my v-if condition.

Step 4: Refactor the imagePath() method to a mixin

I mentioned above that we can reuse the “imagePath()” method previously employed in Home.vue, but such repetition is actually not best practice. Repetitive code takes up unnecessary space and, as an app starts to grow, becomes harder to maintain. We’re only using the method in two places right now, but what if we end up needing it a bunch more times? Wouldn’t it be better to define the method in one place and share that functionality with all the components that need it?

Enter mixins, a flexible way to reuse functionality in Vue components. Besides methods, mixins can include any component option, from computed properties to data.

To make a mixin, create a new folder that exists alongside “views” and “components”. Name it “mixins”. Inside “mixins”, create a new file called imagePath.js with the following contents:

What this file does is export an object named “imagePath”, which contains one method, called “makeImagePath()”. “makeImagePath()” works exactly the same as our previous “imagePath()” method.

To use this mixin, we simply import and register it in the component that needs it. Below, line 26 imports the mixin from the JS file where we defined it (the “@” symbol is shorthand for “/src”). Line 30 registers it, exposing the “makeImagePath()” method we defined. And line 5 is where we call “makeImagePath()” and pass in the product object.

To reiterate what’s happening: instead of defining two different method sections for the Home.vue and Product.vue components, both of which would have the same “imagePath()” method right now, we’re defining it in one place (the mixin) and using a shorthand of

mixins: [imagePath]

to paste in what would otherwise look like

<script>
export default {
...,
methods: {
imagePath(product) {
return require(`../assets/img/products/${product.images[0]}`);
}
}
};
</script>

It’s shorter, more efficient, more scalable, and more reusable. Now, I encourage you to try switching the images on the homepage to use the mixin we just wrote. Remember to:

  1. Import the mixin
  2. Register the mixin
  3. Use the correct method name

Click here to see my solution.

The page should actually look the same afterwards.

Recap

Sweet! We’ve made some great progress:

  • We added a new page to our app, the product detail page.
  • We connected Home.vue to this new page with <router-link>, functionality provided by the Vue Router plugin, and did so using a dynamic route.
  • We learned about getters, which help us directly access store state in our app.
  • And, we leveraged the power of mixins to keep our code lean and reusable.

Next time we’ll add another new page for tracking items in a user’s cart. Hasta luego! 👋

--

--

Nathan Magyar

User Experience Designer and Front End Developer, University of Michigan Office of Academic Innovation