Vue Blog Site - Part 3
Now we'll decompose our Vue components into smaller components. You may not always have to do this, but if your project is highly complex, needs to be extremely well tested, or is created by a large team of developers, then breaking it down into small parts is recommended.
Create a NavBar Component
Create a file named NavBar.vue in the components folder, and add this code to it:
<template>
<div>
<router-link :to="{name:'home'}">Home</router-link>|
<router-link :to="{ name: 'blog-list' }">Blog</router-link>
</div>
</template>
These router-links are referring to routes by their names (remember that when you specify a route object in router.js you can give it a name as well as a path). The router-links that we created previously referred to routes by their path. Using a name, instead of a path, is recommended. When you use this approach, bind the to attribute of the router-link element to an object. And in that object, add a name property, and set it's value to the name of the route that you want to link to.
Update App.vue to use the NavBar component
Make the following changes to App.vue:
Import the NavBar component (inside the SCRIPT element, but before the options object):
import NavBar from "@/components/NavBar.vue";
Register the NavBar by adding a 'components' property to the options object. The options object should look like this when you are done:
export default {
name: "App",
components: {
NavBar,
},
};
Update the template to look like this (note that we are replacing the router-links with the NavBar component that we just created):
<template>
<div>
<nav-bar />
<router-view />
</div>
</template>
Run the app, you should see no changes. But under the hood, the NavBar is now separated into it's own component. Component-based design calls for breaking components up into smaller components so that you can test the smallest components (units), and keep your app flexible. We can discuss this more in class if you like.
Create BlogList and BlogListItem Components
Add two files to the components folder, name them BlogList.vue and BlogListItem.vue.
We'll start with the BlogListItem.vue component, put this code in it:
<template>
<li class="post-item">
<h3>{{ post.title }}</h3>
<p>{{ post.description }}</p>
<span class="author">{{ post.metadata.author }}</span>
<br>
<span class="published">{{ post.published }}</span>
<br>
<router-link :to="{name:'blog-post', params:{postId:post.id}}">Read More</router-link>
</li>
</template>
<script>
export default {
props:["post"]
}
</script>
<style>
.post-item{
background-color: lightgray;
padding: 20px;
margin: 12px;
}
.post-item .author{
color: midnightblue;
}
.post-item .published{
color: midnightblue
}
</style>
Note that the component takes a 'post' object as a prop and displays some properties of the object in the template.
We've also added some CSS code to the component. These styles will affect other components right now. But we could isolate them so that they only affect elements inside this component by adding the scoped attribute to the STYLE element (you can ask me about this in class).
Put this code inside the BlogList.vue component:
<template>
<ul>
<blog-list-item v-for="bp in blogPosts" :key="bp.id" :post="bp" />
</ul>
</template>
<script>
import BlogListItem from './BlogListItem.vue'
export default {
components: { BlogListItem },
props:["blogPosts"]
}
</script>
<style scoped>
ul{
list-style: none;
}
</style>
Note the following about this code:
- It imports the BlogListItem component and uses it in the template with a v-for directive.
- It has a prop named blogPosts which should be an array of post objects.
- The STYLE element includes the scoped attribute, which means that the CSS code will only affect UL elements that are inside this component.
Now lets make use of this component in BlogListView.vue. Replace all the code in BlogListView.vue with this:
<template>
<div>
<h1>This is the blog list page</h1>
<blog-list :blogPosts="posts" />
</div>
</template>
<script>
import {getAllPosts} from '@/blog-data-access'
import BlogList from '@/components/BlogList.vue'
export default {
components: { BlogList },
data(){
return {
posts:[]
}
},
mounted(){
getAllPosts()
.then(resp => {
this.posts = resp;
})
.catch(err => {
console.log(err)
})
}
}
</script>
This version of BlogListView imports the BlogList component and sets the blogPosts prop by passing it's posts data member.
Run the app, and take a look at the blog list page. An end user would never know, but it has been decomposed into smaller parts.
FOLLOW UP - APPLYING STYLES TO COMPONENTS
You can apply CSS code to your components to make them look good. Remember that the styles you apply inside a component will NOT affect other components if the scoped attribute is added to the STYLE element. You may also incorporate Bootstrap and apply the classes in your templates.
To Install Bootstrap
cd into the project folder (vue-blog-site) and run the following commands to install Bootstrap (and it's dependencies):
npm install bootstrap --save
npm install @popperjs/core --save
npm install bootstrap-icons --save
Add these imports to main.js:
import 'bootstrap'
import 'bootstrap/dist/css/bootstrap.min.css'
import "bootstrap-icons/font/bootstrap-icons.css";
You should now be able to apply Bootstrap classes to your templates and use Bootstrap icons.
To verify that Bootstrap is properly installed in the project, update the code in HomeView.vue to look like this (it's making use of a few Bootstrap classes):
<template>
<div>
<h1>Home Page</h1>
<p class="text-secondary">
This paragraph has a Bootstrap class assigned to it
</p>
<button class="btn btn-secondary">This is a Bootstrapped button</button>
</div>
</template>
Run the app to verify that the Bootstrap classes are working in the HomeView.
Other things we could do
If time permits, we might:
- Reformat the date to display as: Jun 1, 2023
- Create pages/views to do CRUD operations on the authors
- Set up the BlogListView component so that it redirects to the 404 component when and invalid postId is passed in.