If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeAt this point, we have access to which category we're currently on and all the category data. Let's use all that goodness to print a better title: we can now print the category name when we're on a category page.
Check out catalog.vue
: this is where the h1
lives. Ok, all we need to do is use the currentCategoryId
to find that category in the categories
data, and then print its name.
To do this, we could create a computed property right in catalog
with all of this logic... and then print it right here. But instead, I'm going to isolate this title area into its own component. Again, when you should move something into its own component is subjective, but I'm planning to reuse this title area in the next tutorial.
In the components/
directory, create a new title.vue
file and start the same way as always: with the template. Set this to a <div>
and immediately add :class="$style.component"
. Inside, put the h1
with, for now, a hardcoded "Products".
<template> | |
<div :class="$style.component"> | |
<h1> | |
Products | |
</h1> | |
</div> | |
</template> | |
... lines 8 - 22 |
Next, add the <script>
tag with the minimum needed: export default
and name: 'Title'
.
... lines 1 - 8 | |
<script> | |
export default { | |
name: 'Title', | |
}; | |
</script> | |
... lines 14 - 22 |
Finally, at the bottom, because we're already referencing $style
, add <style lang="scss" module>
. We only need one thing: .component {}
, an h1 {}
inside, and font-size: 1.7rem
.
... lines 1 - 14 | |
<style lang="scss" module> | |
.component { | |
h1 { | |
font-size: 1.7rem; | |
} | |
} | |
</style> |
Perfecto! A nice, simple component to render the title.
In catalog.vue
, let's use this! But when I import it, I'm going to call it TitleComponent
from @/components/title
. The reason is that, when we add TitleComponent
to components
, it really means TitleComponent: TitleComponent
.
... lines 1 - 19 | |
<script> | |
... lines 21 - 23 | |
import TitleComponent from '@/components/title'; | |
... line 25 | |
export default { | |
... line 27 | |
components: { | |
... lines 29 - 30 | |
TitleComponent, | |
}, | |
... lines 33 - 61 | |
}; | |
</script> |
Anyways, the key - TitleComponent
in this case - determines the HTML element that we can use in the template. TitleComponent
means that we can say <title-component />
.
That's great. But if we had called it Title
, then, in the template, we would need to use <title />
. Do you see the problem? <title>
is a real HTML tag! And so, instead of rendering our title
component, Vue would just... render a title
tag.
In general, if you want to avoid collisions, a best practice - which I admit we have not been following - is to include a -
every time you render a component. There's a W3C spec that recommends that custom component always have a dash.
Anyways, when we use <title-component>
... I think it's working! I still see "Products".
<template> | |
... line 2 | |
<div class="row"> | |
<div class="col-12"> | |
<title-component /> | |
</div> | |
</div> | |
... lines 8 - 17 | |
</template> | |
... lines 19 - 64 |
To make that print the actual category name, something needs to use the currentCategoryId
and categories
info to find the current category's name. For now, I'm going to put that logic directly into title
. That means that title
will need to receive both of these pieces of data as props. Add props:
with currentCategoryId
, type: String
and default: null
so that null is allowed. Then add categories
with type: Array
and required: true
.
... lines 1 - 8 | |
<script> | |
export default { | |
... line 11 | |
props: { | |
currentCategoryId: { | |
type: String, | |
default: null, | |
}, | |
categories: { | |
type: Array, | |
required: true, | |
}, | |
}, | |
}; | |
</script> | |
... lines 24 - 32 |
This is everything we need.
But I want to talk about that categories
prop. Obviously, our title
component will need the categories
Array so it can do its job. By adding it as a prop, you can already see that my plan is to pass this in from the parent component.
But in our app, we know that categories are actually being loaded from the global windows.categories
variable: they are not being loaded via Ajax. They're also not something that will ever change: once we get the categories, those are the categories forever.
So in reality, because the categories are instantly available and never change, instead of using a prop, we could just use our categories-service
to get that data directly from this component!
This is a key thing that I want you to understand: we use props for two reasons.
The first reason is to directly pass configuration to a component. For example, our Legend
component has a title
prop. If you want to use this component, then you need to pass that configuration, which could be a piece of data, a hardcoded string or anything. It's like an argument to a method.
The second reason we use props is related, but more subtle. When something will change during the lifecycle of our Vue app, then that "thing" must live as data on a component. And once something lives as data on a component, the only way to make it accessible to child components is by passing the value as a prop. Props are the only mechanism to pass down data.
Now, side note - with something like Vuex or some new features in Vue 3, it's possible to store data outside of a component in a central location. That helps remove this second reason for using props. But we'll talk about that in our Vue 3 tutorial.
Anyways, in this situation, because categories
is boring and static, it doesn't really need to live as data anywhere. And that means, we do not need to pass it down as props. If we want to, it would be totally ok fetch the categories directly from categories-service
wherever we need it.
Now that you know the easy solution, let's take the harder path. Let's pretend that our categories are still loading via Ajax... which means that technically they do change during our app's lifecycle: they're empty for a moment, and then they populate. Let's tackle this next.
"Houston: no signs of life"
Start the conversation!
// 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
"regenerator-runtime": "^0.13.2", // 0.13.5
"sass": "^1.29.0", // 1.29.0
"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
}
}