Dynamic Categories via AJAX

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

I've gotta say, now that we have dynamic products, these hardcoded categories are starting to stress me out! So let's make those dynamic too!

If you look at /api, just like how we have a /api/products endpoint for the collection of products, we also have one for /api/categories. We can use that! And now that we know the correct way to make Ajax calls to load data, this "should" be simple! Famous last words!

Add an Ajax Call to Sidebar

Open sidebar.vue. We created a data property earlier called categories, but... it's just hardcoded. Set this to an empty array to start.

... lines 1 - 42
<script>
export default {
... lines 45 - 51
data() {
return {
categories: [],
};
},
};
</script>
... lines 59 - 73

To make the AJAX call, head over to catalog.vue to celebrate one of programming's oldest arts: stealing code. Copy the entire created() function and paste it in sidebar.vue under data. We just need to change the URL to /api/categories and the data from this.products to this.categories.

... lines 1 - 42
<script>
import axios from 'axios';
... line 45
export default {
... lines 47 - 58
async created() {
const response = await axios.get('/api/categories');
this.categories = response.data['hydra:member'];
},
};
</script>
... lines 66 - 80

Awesome! Let's... just see what happens! Refresh... whoa! I think it worked! No errors in the console... and in the dev tools, if you click on Sidebar, we have real categories data!

The data for each category is pretty simple: it has the normal @id and @type that comes from JSON-LD and it also has id and name.

Oh, and one thing I forgot to mention: when we pasted the created() function, PhpStorm automatically added the axios import for us. Sweet! Oh, but it used double quotes... which ESLint does not like. Let's fix this. Much better! If you want, you can tweak your PhpStorm settings to use single quotes automatically.

v-for index and :key

Anyways, head up to the v-for directive. We learned earlier that every v-for element must have a key attribute. Until now, since our categories were hardcoded, we used the array index for the :key. But now we can be smarter because we know that each category has a unique @id property.

Let's simplify the v-for - we don't need index anymore - and then say :key="category['@id']".

<template>
... lines 2 - 7
<ul class="nav flex-column mb4">
... lines 9 - 15
<li
v-for="category in categories"
:key="category['@id']"
... line 19
>
... lines 21 - 27
</ul>
... lines 29 - 40
</template>
... lines 42 - 80

Linking Properly

The last thing that doesn't work is... our links! We originally set the href to category.link... but there is no link property on the real category data.

Here's the plan: we will eventually create a separate page that will display all of the products for a specific category - the URL will be /categories/ and then the id of the category. We'll worry about making sure that page exists later, but let's get the links working now.

If you're a Symfony user, then you're used to generating a URL to a route. But from JavaScript, I'm going to keep it simple and hardcode the URL to this new page. I think it's totally ok to do this: it keeps your JavaScript simpler. The tradeoff, of course, is that if you ever changed a URL, you would need to update your JavaScript.

Ok: what we want to do here is say /category/ and then print category.id. But since we have the colon in front of href to make this dynamic, we would need to have single quotes around the string, a plus sign and then category.id.

That would work: in the browser, when I hover over categories, the right URL is display at the bottom of my browser.

JavaScript Dynamic Strings

But yikes! This code hurts to look at! Can we make it nicer? Of course! Replace the quotes with ticks. Now, if we need dynamic code, write ${} - so ${category.id}.

... lines 1 - 7
<ul class="nav flex-column mb4">
... lines 9 - 15
<li
... lines 17 - 19
>
<a
:href="`/category/${category.id}`"
class="nav-link"
>
... line 25
</a>
</li>
</ul>
... lines 29 - 80

This is a superpower of modern JavaScript, not Vue. Look over now and... yes! All of the links look perfect!

Too much Dynamic Data!

This is working nicely! We have dynamic products and dynamic categories. The only thing that bothers me is all the loading! Notice that each time we refresh, I see the products and categories loading!

When we only loaded the products, that was probably okay: it was just one spot that loaded pretty fast. But also having the categories waiting to load is starting to look a bit jarring. Plus, we're eventually going to have multiple pages that will use the same categories sidebar. This means that on every page, the user will wait for the same list of categories to be fetched via AJAX. We can do better!

So next, let's investigate how we can get data from the server in a way that avoids an AJAX call. We'll use this at first to highlight the current category on the sidebar. Then later, we'll replace the AJAX call completely.

Leave a comment!

This course is also built to work with Vue 3!

What JavaScript libraries does this tutorial use?

// package.json
{
    "devDependencies": {
        "@symfony/webpack-encore": "^0.30.0", // 0.30.2
        "axios": "^0.19.2", // 0.19.2
        "bootstrap": "^4.4.1", // 4.5.0
        "core-js": "^3.0.0", // 3.6.5
        "eslint": "^6.7.2", // 6.8.0
        "eslint-config-airbnb-base": "^14.0.0", // 14.1.0
        "eslint-plugin-import": "^2.19.1", // 2.20.2
        "eslint-plugin-vue": "^6.0.1", // 6.2.2
        "node-sass": "^4.13.0", // 4.14.1
        "regenerator-runtime": "^0.13.2", // 0.13.5
        "sass-loader": "^8.0.0", // 8.0.2
        "vue": "^2.6.11", // 2.6.11
        "vue-loader": "^15.9.1", // 15.9.2
        "vue-template-compiler": "^2.6.11", // 2.6.11
        "webpack-notifier": "^1.6.0" // 1.8.0
    }
}