A Beginner’s Guide to Working With Components in Vue

Share this article

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.

Frequently Asked Questions (FAQs) about Vue Components

What are the key differences between Vue.js and other JavaScript frameworks?

Vue.js is a progressive JavaScript framework that is designed to be incrementally adoptable. This means that you can use as much or as little of Vue.js as you need for your project. It’s also very lightweight and fast, which makes it a great choice for performance-sensitive applications. Unlike other frameworks like Angular or React, Vue.js also includes a lot of features out of the box, such as a template system and a system for managing state, which can make it easier to get started with.

How do I create a Vue component?

Creating a Vue component is straightforward. First, you need to define a new Vue instance with the Vue.component method. This method takes two arguments: the name of the component and an options object. The options object can include data, methods, computed properties, lifecycle hooks, and more. Here’s a basic example:

Vue.component('my-component', {
data: function() {
return {
message: 'Hello, world!'
}
},
template: '<div>{{ message }}</div>'
})

How do I use a Vue component in my application?

Once you’ve defined a Vue component, you can use it in your application by including it in your Vue instance’s template. You do this by using the component’s name as a custom HTML element. For example, if you’ve defined a component named ‘my-component’, you can use it like this:

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

How do I pass data to a Vue component?

You can pass data to a Vue component using props. Props are custom attributes that you can register on a component. When a value is passed to a prop, it becomes a property on that component instance. To pass a prop to a component, you include it as an attribute in the component’s custom HTML element. Here’s an example:

<my-component message="Hello, world!"></my-component>

How do I handle events in a Vue component?

You can handle events in a Vue component using the v-on directive. This directive listens for a specified event and runs a method when that event is triggered. Here’s an example:

<button v-on:click="sayHello">Say hello</button>

In this example, when the button is clicked, the sayHello method will be run.

How do I use computed properties in a Vue component?

Computed properties are a powerful feature of Vue.js that allow you to create reusable pieces of logic that can be used in your templates. To use a computed property, you define it in the computed property of your component’s options object. Here’s an example:

Vue.component('my-component', {
data: function() {
return {
firstName: 'John',
lastName: 'Doe'
}
},
computed: {
fullName: function() {
return this.firstName + ' ' + this.lastName;
}
},
template: '<div>{{ fullName }}</div>'
})

In this example, the fullName computed property is used in the template to display the full name of the person.

How do I use lifecycle hooks in a Vue component?

Lifecycle hooks are a feature of Vue.js that allow you to run code at specific stages in the lifecycle of a component. There are several lifecycle hooks available, including beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, beforeDestroy, and destroyed. To use a lifecycle hook, you define it as a method in your component’s options object. Here’s an example:

Vue.component('my-component', {
created: function() {
console.log('Component has been created!')
}
})

In this example, the created lifecycle hook is used to log a message to the console when the component is created.

How do I use Vue Router with Vue components?

Vue Router is a powerful tool for building single-page applications with Vue.js. To use Vue Router with Vue components, you first need to install and import Vue Router. Then, you define your routes as an array of route objects, each of which maps a path to a component. Finally, you create a new Vue Router instance with your routes and pass it to your main Vue instance. Here’s an example:

import Vue from 'vue'
import VueRouter from 'vue-router'
import MyComponent from './MyComponent.vue'

Vue.use(VueRouter)

const routes = [
{ path: '/my-component', component: MyComponent }
]

const router = new VueRouter({
routes
})

new Vue({
router
}).$mount('#app')

In this example, when the user navigates to ‘/my-component’, the MyComponent component will be rendered.

How do I use Vuex with Vue components?

Vuex is a state management pattern and library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion. To use Vuex with Vue components, you first need to install and import Vuex. Then, you create a new Vuex store with your state, mutations, actions, and getters. Finally, you pass your store to your main Vue instance. Here’s an example:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})

new Vue({
store
}).$mount('#app')

In this example, the count state is incremented by the increment mutation.

How do I test Vue components?

Testing Vue components can be done using a variety of tools and techniques. One popular approach is to use Jest, a JavaScript testing framework, in combination with Vue Test Utils, a set of utility functions for testing Vue components. With these tools, you can write unit tests for your components that verify their behavior under different conditions. Here’s an example:

import { shallowMount } from '@vue/test-utils'
import MyComponent from './MyComponent.vue'

describe('MyComponent', () => {
it('renders a message', () => {
const wrapper = shallowMount(MyComponent, {
propsData: { message: 'Hello, world!' }
})
expect(wrapper.text()).toMatch('Hello, world!')
})
})

In this example, a test is written for the MyComponent component that checks if it correctly renders a message.

Kingsley SilasKingsley Silas
View Author

Kingsley Silas is a web developer from Nigeria. He has a hunger for acquiring new knowledge in every aspect he finds interesting.

componentsvuevue-hub
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week