Building an Online Store with Vue CLI — Part 6
Practicing Getters, Computed Properties, and Abstract Page Components
Part 1 | Part 2 | Part 3 | Part 4 | Part 5 | Part 6:
In the previous article we used computed properties to mimic the checkout process for our online store. This post will give you more practice with computed properties, getters, and the process of creating a generic page component that displays products for a specific gender.
Step 1: Add more dummy data
Go ahead and copy/paste the following store.js
file into your project. It includes 6 new products, and adds a new featured
property to every object in the products
array. The first four have their featured
property marked as true
(meaning we want to feature them) and the others have a value of false
.
Test Your Knowledge: Quiz Question 1
You can find the updated products
directory of images here. Make sure to place them in the same place: online-store/src/assets/img/products
.
Cool! New products! Except when you view the homepage of your project…eek! Too many featured items:
Step 2: Create a featuredProducts getter
At the bottom of store.js
inside your getters
object, create a new getter to retrieve only store products that have featured
marked as true
.
Then, in Home.vue
, replace the products
computed property with one named featuredProducts
. Its return value should be the featuredProducts
getter you just created (aka an array of products whose featured
property is true
).
That’s looking better, but I preferred when we were only showing three products in this section. And…what if our store one day has a much higher number of products with featured
marked as true
? We’d wind up with the same scenario we just had after adding the dummy data.
Let’s fix this by limiting the number of number of featured products to three:
featuredProducts: function() {
return this.$store.getters.featuredProducts.slice(0,3)
}
On the end of the return statement add the slice
method, which returns a new array of items based on a starting index and an ending index for the original array. Note: javascript will not include the item found at the ending index. It stops at the one just before it.
Test Your Knowledge: Quiz Question 2
Step 3: Create a GenderOverview page component
Next we will be making one page that is capable of displaying either women’s and men’s items only, depending on the URL parameters (aka if ‘men’ is in the URL or ‘women’ is in the URL). The reason we’re creating one component for both of these pages is because although they will have different products, their structure and behavior will be the same. This keeps our code DRY (don’t repeat yourself).
Inside the views
directory create a component called GenderOverview.vue
. For now you can give it a hardcoded title in the template:
In router.js
, we’ll import the GenderOverview
component and create a new route object that is similar to the one used for theproducts
page. This time, instead of passing in a productId
, we’ll pass in a string that specifies the gender
of products to display for the page, hence the path of '/:gender/'
.
Now let’s add a new router-link
element to our navigation in App.vue
. Remember that since it’s a dynamic route we have to use more complex syntax for router-link
's to
attribute. The added router-link
below will take users to a page for women’s products:
Test Your Knowledge: Quiz Question 3
Step 4: Add gender-related computed properties
Since this component is designed to display products for men or women, it needs a way to find out which gender of products the user wants to see. We can do this by writing a computed property in GenderOverview.vue
that accesses the gender
param of our route, like so:
...
computed: {
gender() {
return this.$route.params.gender
},
}
...
From there, we can make a dynamic page title that looks at the gender
computed property and displays that string in a title case format:
computed: {
...,
pageTitle() {
return `${this.gender[0].toUpperCase()}${this.gender.slice(1)}`
},
The above approach uses template literals, string indexing, and the slice
method to create a title case string, assuming the string is only 1 word. Feel free to write it differently so that multi-word titles are also properly formatted! Note: this time the slice
method only has one argument, the starting index. When this happens, it copies the remainder of the string from the starting index to the end, which is what we want.
Add the pageTitle
computed property to your template in place of the previous hardcoded title:
<template>
...
<h1 class="wrapper">{{ pageTitle }}</h1>
...
</template>
Test Your Knowledge: Quiz Question 4
Step 5: Add a productsByGender computed property and getter
For both the men’s and women’s pages, we’re going to need to filter all the products in the store by the appropriate gender. We could do this directly in the GenderOverview
component, since right now no other component needs this type of functionality, but for practice purposes let’s leave this logic to a getter. And who knows, maybe we’ll create another feature in the future that does need this getter and we’ll already have the functionality in a more globally accessible place.
Computed properties vs getters: which one do I use and when? You can often answer this question by asking yourself: do other parts of my application need access to this information? If so, you should put that function in a more global place where all of them can find it, like a
getter
instore.js
. If not, it’s fine to leave the logic/functionality where it is and wait until you have a reason to abstract it out into a higher level component or the store later.
Again though, we’re practicing getters
here so that’s what we’ll do.
Inside GenderOverview.vue
, create a generic productsByGender
computed property that calls a getter by the same name. This particular getter
should take the gender
computed property as an argument. We pass in this.gender
so the getter will know by which gender to filter our products.
computed: {
...,
productsByGender() {
return this.$store.getters.productsByGender(this.gender)
}
}
In store.js
, add the getter to which you just referred. Because we’re passing an argument to this getter (the gender), there’s an extra-looking layer to this function. What’s happening is our getter is itself returning another function, which actually filters state.products
by the gender
we passed in:
getters: {
...,
productsByGender: (state) => (gender) => {
return state.products.filter(p => p.gender === gender)
},
productsByCategory: (category) => {
return this.state.products.filter(p => p.category === category)
}
}
Test Your Knowledge: Quiz Questions 5 and 6
Step 6: Add template markup and styles
Next, flesh out the GenderOverview.vue
template and styles. Similar to the featuredProducts
computed property in Home.vue
, we’re looping through the productsByGender
computed property on this page, displaying the each product’s image, name, and price, and linking to its respective detail page with a router-link
:
Test Your Knowledge: Quiz Question 7
Step 7: Add the men’s page
Inside App.vue
, add a <router-link>
element for the men’s page after the women’s link:
<router-link
:to="{ name: 'gender-overview', params: { gender: 'men'}}" class="nav-items__item">Men</router-link>
All the functionality is already in place, so the men’s page should automatically look like this below:
Tada! We just knocked out two new pages of our app with one component, each time using the same computed properties and getters. Well done!
Next up we’ll add a new widget to this component that allows the user to receive a set of random product recommendations. 👋