Video:

Part 6 - Advanced Components

March 19, 2018

Course Instructor: Elliot Forbes

Hey Gophers! My name is Elliot and I'm the creator of TutorialEdge and I've been working with Go systems for roughly 5 years now.

As we continue to build this HackerNews clone up, the code within some of our components is going to increasingly grow. We need to start splitting our application up into multiple smaller components and in order for us to do this, we’ll first have to learn some new concepts such as passing data into components using props.

In this tutorial, we are going to create an Item.vue component that will render a single item within our Homepage.vue component. The finished product of this will look like this:

Note: We will be stealing a lot of the design elements from the official HackerNews project: vue-hackernews-2.0

Item.vue

The first thing we will need to do is to define our Item.vue component. This will take in a story and will render it for us using the css that we will define in the <style> tags.

<template>
  <div class="story">
    <span class="score">{{ story.data.score }}</span>
    <router-link :to="{ path: '/story/' + story.data.id }"
      >{{ story.data.title }}<span
        >{{ story.data.url | host }}</span
      ></router-link
    ><br />
    <span class="meta">
      by {{ story.data.by }} | {{ story.data.time }} Ago | {{
      story.data.descendants }} comments
    </span>
  </div>
</template>

<script>
  export default {
    name: "Item",
    props: ["story"]
  };
</script>

<style scoped>
  .story {
    background-color: #fff;
    padding: 20px 30px 20px 80px;
    border-bottom: 1px solid #eee;
    position: relative;
    line-height: 20px;
  }
  .score {
    color: #f60;
    font-size: 1.1em;
    font-weight: 700;
    position: absolute;
    top: 50%;
    left: 0;
    width: 80px;
    text-align: center;
    margin-top: -10px;
  }
  .story a {
    color: #34495e;
    font-weight: 600;
    text-decoration: none;
  }
  .story a span {
    font-size: 0.85em;
    margin-left: 10px;
    color: #828282;
  }
  .story .meta {
    font-size: 0.85em;
    color: #828282;
  }
</style>

Notice that we’ve used props within our component’s js. This will allow us to pass in a story object further down the line from a parent component such as our Home.vue component.

Now that we have created this new component, we can go about updating our Home.vue component so that it uses our newly defined Item.vue component.

Updating our Home.vue

Let us first modify our <template> tags so that we utilize our new Item.vue component. We’ll still want to reuse the v-for VueJS directive in order to render an array of top items.

We use :story to bind an individual story object to each of our Item components.

<template>
  <div class="container">
    <item v-for="story in stories" :key="story.data.id" :story="story"></item>
  </div>
</template>

This looks a lot more succinct and is positively far better than just having our Home.vue component constantly grow on us.

Now that we’ve done this, we need to import our Item component and register it within our Home component. We can do this like so:

import axios from 'axios'
import Item from '@/views/Item.vue'

export default {
  name: 'Homepage',
  components: {
    'item': Item
  },
  data: function () {
    return {
      err: '',
      stories: []
    }
  },
  // ... the rest of our Home.vue component

Nothing else within our Home.vue component needs to change. If you save all of the changes you have just made then you should see your application rendering nicely in the browser.

Why Break Up Our Application?

So at this point, I think it’s worthwhile covering why we have just made a smaller Item.vue component instead of just pushing all our code into one component. By doing this, we effectively enable ourselves to reuse various parts of our codebase in different places. It also helps to break up a massive application into a series of smaller, easier to debug, digest and expand upon.

Let’s see a perfect example of this now. Create a new component called New.vue, this is going to show off all of the newest articles available courtesy of the HackerNews API.

Within our <template> tags I want you to add the following:

<template>
  <div class="container">
    <item v-for="story in stories" :key="story.data.id" :story="story"></item>
  </div>
</template>

You may notice that this is identical to the code we currently have in our Home.vue component and you would be 100% right. We now want to update our <script> element to look like this:

import axios from "axios";
import Item from "@/views/Item.vue";

export default {
  name: "New",
  components: {
    item: Item
  },
  data: function() {
    return {
      err: "",
      stories: []
    };
  },
  created: function() {
    axios
      .get("https://hacker-news.firebaseio.com/v0/newstories.json")
      .then(result => {
        this.results = result.data.slice(0, 25);
        this.results.forEach(element => {
          axios
            .get(
              "https://hacker-news.firebaseio.com/v0/item/" + element + ".json"
            )
            .then(result => {
              this.stories.push(result);
            })
            .catch(err => {
              console.log(err);
            });
        });
      })
      .catch(err => {
        this.err = err;
      });
  }
};

Again, this should look identical to our Home.vue component, except for the fact that we are hitting the https://hacker=news.firebaseio.com/v0/newstories.json API endpoint as opposed to the topstories.json endpoint.

Adding a ‘New’ Route

We need a new route to render our New.vue component, so fire open your /src/router/index.js and update it to have a route that maps to our New component like so:

import Vue from "vue";
import Router from "vue-router";
import Home from "./views/Home.vue";
import Single from "./views/Single.vue";
import New from "./views/New.vue";

Vue.use(Router);

export default new Router({
  routes: [
    {
      path: "/",
      name: "Homepage",
      component: Homepage
    },
    {
      path: "/story/:id",
      name: "Single",
      component: Single
    },
    {
      path: "/new",
      name: "New",
      component: New
    }
  ]
});

Updating our Navbar.vue

Finally, let’s update our Navbar.vue component so that we have a link to not only our Homepage, but also to /new so that we can decide when we want to view new stories or top stories:

<template>
  <div class="pure-menu pure-menu-horizontal">
    <div class="container">
      <router-link :to="{ path: '/' }" class="pure-menu-heading pure-menu-link"
        >Home</router-link
      >
      <ul class="pure-menu-list">
        <li class="pure-menu-item">
          <router-link :to="{ path: '/new' }" class="pure-menu-link"
            >New</router-link
          >
        </li>
      </ul>
    </div>
  </div>
</template>

<script>
  export default {
    name: "Navbar"
  };
</script>

<style scoped>
  .pure-menu {
    background-color: #e17842;
  }
  .pure-menu a {
    color: white;
  }
</style>

Conclusion

So, in this tutorial, we looked at how we could further break up our growing application into more components and start passing information from a parent component, our Home.vue component, to individual Item.vue components.

We’ve covered some of the main benefits of breaking down a large, growing application into a series of smaller components. We also looked at how we could pass information between both a parent component and a series of child components using props.

In the next tutorial, we are going to look at how we can manage state within our VueJS application using Vuex and improve the performance of our application by caching API results. You can see the next tutorial here: Part 7 - Managing State with Vuex