A Beginner’s Guide to Working With Components in Vue

One of the great things about working with Vue is its component-based approach to building user interfaces. This allows you to break your application into smaller, reusable pieces (components) which you can then use to build out a more complicated structure.

In this guide, I’ll offer you a high-level introduction to working with components in Vue. I’ll look at how to create components, how to pass data between components (via both props and an event bus) and how to use Vue’s <slot> element to render additional content within a component.

Each example will be accompanied by a runnable CodePen demo.

How to Create Components in Vue

Components are essentially reusable Vue instances with a name. There are various ways to create components within a Vue application. For example, in a small- to medium-sized project you can use the Vue.component method to register a global component, like so:

Vue.component('my-counter', {
  data() {
    return {
      count: 0
    }
  },
  template: `<div>{{ count }}</div>`
})

new Vue({ el: '#app' })

The name of the component is my-counter. It can be used like so:

<div id="app">
  <my-counter></my-counter>
</div>

When naming your component, you can choose kebab case (my-custom-component) or Pascal case (MyCustomComponent). You can use either variation when referencing your component from within a template, but when referencing it directly in the DOM (as in the example above), only the kebab case tag name is valid.

You might also notice that, in the example above, data is a function which returns an object literal (as opposed to being an object literal itself). This is so that each instance of the component receives its own data object and doesn’t have to share one global instance with all other instances.

There are several ways to define a component template. Above we are using a template literal, but we could also use a <script tag> marked with text/x-template or an in-DOM template. You can read more about the different ways of defining templates here.

Want to learn Vue.js from the ground up? This article is an extract from our Premium library. Get an entire collection of Vue books covering fundamentals, projects, tips and tools & more with SitePoint Premium. Join now for just $9/month.

Single-file Components

In more complex projects, global components can quickly become unwieldy. In such cases, it makes sense to craft your application to use single-file components. As the name suggests, these are single files with a .vue extension, which contain a <template>, <script> and <style> section.

For our example above, an App component might look like this:

<template>
  <div id="app">
    <my-counter></my-counter>
  </div>
</template>

<script>
import myCounter from './components/myCounter.vue'

export default {
  name: 'app',
  components: { myCounter }
}
</script>

<style></style>

And a MyCounter component might look like this:

<template>
  <div>{{ count }}</div>
</template>

<script>
export default {
  name: 'my-counter',
  data() {
    return {
      count: 0
    }
  }
}
</script>

<style></style>

As you can see, when using single-file components, it’s possible to import and use these directly within the components where they’re needed.

In this guide, I’ll present all of the examples using the Vue.component() method of registering a component.

Using single-file components generally involves a build step (for example, with Vue CLI). If this is something you’d like to find out more about, please check out “A Beginner’s Guide to Vue CLI” in this Vue series.

Passing Data to Components Via Props

Props enable us to pass data from a parent component to child component. This makes it possible for our components to be in smaller chunks to handle specific functionalities. For example, if we have a blog component we might want to display information such as the author’s details, post details (title, body and images) and comments.

We can break these into child components, so that each component handles specific data, making the component tree look like this:

<BlogPost>
  <AuthorDetails></AuthorDetails>
  <PostDetails></PostDetails>
  <Comments></Comments>
</BlogPost>

If you’re still not convinced about the benefits of using components, take a moment to realize how useful this kind of composition can be. If you were to revisit this code in the future, it would be immediately obvious how the page is structured and where (that is, in which component) you should look for which functionality. This declarative way of composing an interface also makes it much easier for someone who isn’t familiar with a codebase to dive in and become productive quickly.

Since all the data will be passed from the parent component, it can look like this:

new Vue({
  el: '#app',
  data() {
    return {
      author: {
        name: 'John Doe',
        email: 'jdoe@example.com'
      }
    }
  }
})

In the above component, we have the author details and post information defined. Next, we have to create the child component. Let’s call the child component author-detail. So our HTML template will look like this:

<div id="app">
  <author-detail :owner="author"></author-detail>
</div>

We’re passing the child component the author object as props with the name owner. It’s important to note the difference here. In the child component, owner is the name of the prop with which we receive the data from the parent component. The data we want to receive is called author, which we’ve defined in our parent component.

To have access to this data, we need to declare the props in the author-detail component:

Vue.component('author-detail', {
  template: `
    <div>
      <h2>{{ owner.name }}</h2>
      <p>{{ owner.email }}</p>
    </div>
  ´,
  props: ['owner']
})

We can also enable validation when passing props, to make sure the right data is being passed. This is similar to PropTypes in React. To enable validation in the above example, change our component to look like this:

Vue.component('author-detail', {
  template: `
    <div>
      <h2>{{ owner.name }}</h2>
      <p>{{ owner.email }}</p>
    </div>
  `,
  props: {
    owner: {
      type: Object,
      required: true
    }
  }
})

If we pass the wrong prop type, you’ll see an error in your console that looks like what I have below:

"[Vue warn]: Invalid prop: type check failed for prop 'text'. Expected Boolean, got String.
(found in component <>)"

There’s an official guide in the Vue docs that you can use to learn about prop validation.

See the Pen Vue Componets – Props by SitePoint (@SitePoint) on CodePen.

Communicating From a Child to Parent Component via an Event Bus

Events are handled by creating wrapper methods that are triggered when the chosen event takes place. By way of a refresher, let’s build on our original counter example, so that it increases each time a button is clicked.

This is what our component should look like:

new Vue({
  el: '#app',
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
})

And our template:

<div id="app">
  {{ count }}
  <div>
    <button @click="increment">+</button>
  </div>
</div>

This is hopefully simple enough. As you can see, we’re hooking into the onClick event to trigger a custom increase method whenever the button is clicked. The increase method is then incrementing our count data property. Now let’s expand the example to move the counter button into a separate component and display the count in the parent. We can do this using an event bus.

Event buses come in handy when you want to communicate from a child component to parent component. This is contrary to the default method of communication, which happens from parent to child. You can make use of an event bus if your application isn’t big enough to require the use of Vuex. (You can read more about that in “Getting Started with Vuex: a Beginner’s Guide” in this Vue series.)

So here’s what we want to do: the count will be declared in the parent component and passed down to a child component. Then in the child component, we want to increment the value of count and also ensure that the value is updated in the parent component.

The App component will look like this:

new Vue({
  el: '#app',
  data() {
    return {
      count: 0
    }
  }

Then in the child component, we want to receive the count via props and have a method to increment it. We don’t want to display the value of count in the child component. We only want to do the increment from the child component and have it reflected in the parent component:

Vue.component('counter', {
  template: `
    <div>
      <button @click="increment">+</button>
    </div>
  `,
  props: {
    value: {
      type: Number,
      required: true
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
})

Then our template will look like this:

<div id="app">
  <h3>
    {{ count }}
  </h3>
  <counter :count="count" />
</div>

If you try incrementing the value like that, it won’t work. To make it work, we have to emit an event from the child component, send the new value of count and also listen for this event in the parent component.

First, we create a new instance of Vue and set it to eventBus:

const eventBus = new Vue();

We can now make use of the event bus in our component. The child component will look like this:

Vue.component('counter', {
  props: {
    count: {
      type: Number,
      required: true
    }
  },
  methods: {
    increment() {
      this.count++
      eventBus.$emit('count-incremented', this.count)
    }
  },
  template: `
  <div>
    <button @click="increment">+</button>
  </div>
  `
})

The event is emitted each time the increment method is called. We have to listen for the event in the main component and then set count to the value we obtained through the event that was emitted:

new Vue({
  el: '#app',
  data() {
    return {
      count: 0
    }
  },
  created() {
    eventBus.$on('count-incremented', (count) => {
      this.count = count
    })
  }
})

Note that we’re making use of Vue’s created lifecycle method to hook into the component before it’s mounted and to set up the event bus.

Using an event bus is good if your application isn’t complex, but please remember that, as your application grows, you may need to make use of Vuex instead.

See the Pen Vue Components – Event Bus by SitePoint (@SitePoint) on CodePen.

Nesting Content in Components Using Slots

In all the examples we’ve seen so far, the components have been self-closing elements. However, in order to make components that can be composed together in useful ways, we need to be able to nest them inside one another as we do with HTML elements.

If you try using a component with a closing tag and putting some content inside, you’ll see that Vue just swallows this up. Anything within the component’s opening and closing tags is replaced with the rendered output from the component itself:

<div id="app">
  <author-detail :owner="author">
    <p>This will be replaced</p>
  </author-detail>
</div>

Luckily, Vue’s slots make it possible to pass an arbitrary value to a component. This can be anything from DOM elements from a parent component to a child component. Let’s see how they work.

The script part of our components will look like this:

Vue.component('list', {
  template: '#list'
})

new Vue({
  el: "#app"
})

Then the templates will look like this:

<div id="app">
  <h2>Slots</h2>
  <list>
    <h4>I am the first slot</h4>
  </list>
  <list>
    <h4>I am the second slot</h4>
  </list>
</div>

<script type="text/x-template" id="list">
  <div>
    <h3>Child Component</h3>
    <slot></slot>
  </div>
</script>

The content inside our <list> component gets rendered between the <slot></slot> element tag. We can also make use of fallback content, for cases where the parent doesn’t inject any.

<div id="app">
  <h2>Slots</h2>
  <list>
    <h4>I am the first slot</h4>
  </list>
  <list></list>
</div>

<script type="text/x-template" id="list">
  <div>
    <h3>Child Component</h3>
    <slot>This is fallback content</slot>
  </div>
</script>

The fallback content will render in cases where there’s no content from the parent component.

See the Pen Vue Components – Slots by SitePoint (@SitePoint) on CodePen.

Conclusion

This has been a high-level introduction to working with components in Vue. We looked at how to create components in Vue, how to communicate from a parent to a child component via props and from a child to a parent via an event bus. We then finished off by looking at slots, a handy method for composing components in useful ways. I hope you’ve found the tutorial useful.