Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Organizing into more Components

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

One of the huge benefits of Vue is being able to organize your DOM elements and their behavior into smaller pieces. Our app is still pretty simple but you can already see that our template is getting a bit crowded. Could we break this into sub-components?

Let's see: this really looks like two parts: a sidebar and a main area that will eventually list products - I'm going to call that the "catalog". Ok! Let's create two new components to keep things organized.

Creating the Catalog Component

Start with the catalog. Inside the components/ directory - because this will technically be a component that we could re-use, create a new file called catalog.vue.

Then we always start the same - I love when things are boring! Add a <template> tag - with an empty <div></div> to start - and a <script> tag. The minimum we need here is export default an object. To help debugging, we always include at least a name key that identifies this component.

<template>
... lines 2 - 20
</template>
... lines 22 - 25
export default {
name: 'Catalog',
... lines 28 - 33
};
</script>

Nice start team! Let's go grab the parts that we want to move here. Let's see: I want the Products title, the area where that will list products and the legend. Basically, I want to move this entire col-xs-12 div. But... I won't exactly do that. It's up to you, but I think the col-xs-12 belongs in this template because it defines the "layout" for products page. In theory, the catalog component could be embedded into any area on the site, so I won't put this element in there.

Instead, copy the 3 divs inside of this element. Back in catalog.vue, I'm going to keep the empty div. Why? One of the rules of Vue is that you must have a single outer element in each component. If we deleted the div, we would have three outer elements and... you won't be friends with Vue anymore! There is a way to fix this in Vue 3 - called Fragments - and you can even use a a fragments plugin for Vue 2 if you really want to avoid this. But we'll keep the extra div.

<template>
<div>
<div class="row">
<div class="col-12">
<h1>
Products
</h1>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-6 mb-2 pb-2">
TODO - load some products!
</div>
</div>
<div class="row">
<legend-component :title="legend" />
</div>
</div>
</template>
... lines 22 - 36

In the template, we're using <legend-component /> so we need to import that just like we did before - import LegendComponent from ./legend - and add a components option with that inside.

... lines 1 - 22
<script>
import LegendComponent from './legend';
... line 25
export default {
... line 27
components: {
LegendComponent,
},
... lines 31 - 33
};
</script>

Perfect: LegendComponent down here, allows us to use legend-component in the template.

The last thing we need is our data: this is the text that we pass to LegendComponent. Copy the data() function from products.vue and paste it here.

... lines 1 - 25
export default {
... lines 27 - 30
data: () => ({
legend: 'Shipping takes 10-12 weeks, and products probably won\'t work',
}),
};
... lines 35 - 36

Now we could have kept this data key in products and passed it into catalog as a prop. We're going to talk more later about where a piece of data should live and why. But don't worry about that yet.

Creating the Sidebar Component

Ok! I think this component is ready! Let's do the sidebar. Create a new file called sidebar.vue and start the same way: with a <template> tag. This time, let's immediately grab the elements we need. Like last time, I'm going to leave the col-xs-12 here so that the parent component can determine the layout. Copy the <div> inside and paste it into sidebar.vue.

<template>
<div :class="[$style.sidebar, 'p-3', 'mb-5']">
<h5 class="text-center">
Categories
</h5>
<ul class="nav flex-column mb4">
<li class="nav-item">
<a
class="nav-link"
href="/"
>All Products</a>
</li>
<li class="nav-item">
<a
class="nav-link"
href="#"
>Category A</a>
</li>
<li class="nav-item">
<a
class="nav-link"
href="#"
>Category B</a>
</li>
</ul>
</div>
</template>
... lines 29 - 49

Perfect! Next, the <script> tag with export default, name: 'Sidebar' and... that's all the config this component needs!

... lines 1 - 29
<script>
export default {
name: 'Sidebar',
};
</script>
... lines 35 - 49

But the sidebar does need one more thing: some styles. In products.vue, all the way at the bottom, copy the entire style tag and put it into sidebar.vue.

... lines 1 - 35
<style lang="scss" module>
@import '../../scss/components/light-component';
.sidebar {
@include light-component;
ul {
li a:hover {
background: $blue-component-link-hover;
}
}
}
</style>

Cleaning up the Parent Component

I think we're ready! In products.vue, this is going to be so satisfying. Remove the old import and replace it with import Catalog from and go up one level ../components/catalog. Copy that and also import Sidebar from sidebar. Add both of these to the components option to make them available in the template.

... lines 1 - 14
<script>
import Catalog from '../components/catalog';
import Sidebar from '../components/sidebar';
... line 18
export default {
... line 20
components: {
Catalog,
Sidebar,
},
};
</script>

Ready to delete some code? Remove all the sidebar markup and instead say: <sidebar />. Do the same for the 3 catalog divs: just <catalog />.

<template>
<div class="container-fluid">
<div class="row">
<aside class="col-xs-12 col-3">
<sidebar />
</aside>
<div class="col-xs-12 col-9">
<catalog />
</div>
</div>
</div>
</template>
... lines 14 - 27

In the component itself, if you look at data(), the legend key is no longer used directly in this component... so we can delete the whole function. Also remove the style tag.

Wow. Look at that! Down to about 25 lines of code! Deciding when a component should be split into smaller components isn't a science. In this case, none of the code we removed was complex, but splitting it allowed us to organize a lot of HTML and styles. I can think more clearly now.

Let's see if it works! Thanks to the dev-server that's running in hot mode, we don't even need to refresh: it does work. But... I'll refresh just to prove it.

Next: as our app grows, there will be more and more directories and paths to keep track of, like going to ../components or ../../scss. That's not a huge deal, but we can add a shortcut to make life easier.

Leave a comment!

0
Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

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