w3resource
Vue Tutorial

Components Basics

Base Example

An example of Vue component is given below:

// this defines a new component called button-counter
Vue.component('button-counter', {
  data: function () {
    return {
      count: 0
    }
  },
  template: '<button v-on:click="count++">I was clicked me {{ count }} times.</button>'
})

Components are reusable Vue components that has a name: in the above case, <button-counter>. We can the component as a custom element inside a root Vue instance that was created with new Vue:

HTML

<div id="demo">
  <button-counter></button-counter>
</div>

JS

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

Because components are Vue instances that are reusable, they will accept the same options as new Vue, such as data, computed, watch, lifecycle hooks and methods. The only exception to this are few root-specific options like el.

Reusing Components

Components can be used as many times as we want:

<div id="demo">
  <button-counter></button-counter>
  <button-counter></button-counter>
  <button-counter></button-counter>
</div>

Whenever we use a component, a new instance of the component is created.

data Must Be a Function

In the definition of the <button-counter> component above, notice that the data wasn?t directly provided an object, like so:

data: {
  count: 0
}

Rather, a component?s data option should be a function, so that each instance will maintain an independent copy of the returned data object:

data: function () {
  return {
    count: 0
  }
}

If we didn't have the rule in the later, we will trigger a change of the data of all other instances.

Organizing Components

Apps are commonly organized in tree of nested components:

For Instance, you can have components for a header, content area, and sidebar, each typically containing other components for blog posts, navigation links, etc.

To use these components, you must register them. This registration could be either globally or locally. For global registration the syntax is:

Vue.component('my-component-name', {
  // ... options ...
})

Passing Data to Child Components with Props

Components are useless if we can?t pass data to them. To pass data we use props. Props are custom attributes that you can register on a component. When we pass a value to a prop attribute, it will become a property on that component instance:

Vue.component('blog-post', {
  props: ['title'],
  template: '<h3>{{ title }}</h3>'
})

A component can have more than one prop and any can be passed on to any prop.

When we register a prop, we can pass the data to it as a custom attribute:>

<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>

However, in a typical app, one will likely have an array of posts in a data:

new Vue({
  el: '#blog-post-demo',
  data: {
    posts: [
      { id: 1, title: 'My journey with Vue' },
      { id: 2, title: 'Blogging with Vue' },
      { id: 3, title: 'Why Vue is so fun' }
    ]
  }
})

And then render a component for each post:

<blog-post
  v-for="post in posts"
  v-bind:key="post.id"
  v-bind:title="post.title"
>>/blog-post>

We can always use v-bind directive to dynamically pass props.

A Single Root Element

When we are building out a <blog-post> component, our template will not just contain the title.

It should include at least the posts content. But running the snippet below:

<h3>{{ title }}</h3>
<div v-html="content"></div>

We will get a every component must have a single root element error. This is fixed by wrapping the template in a parent element.

When the scale of the component increase below is a good practice:

<blog-post
  v-for="post in posts"
  v-bind:key="post.id"
  v-bind:post="post"
></blog-post>
Vue.component('blog-post', {
  props: ['post'],
  template: `
    <div class="blog-post">
      <h3>{{ post.title }}</h3>
      <div v-html="post.content"></div>
    </div>
})

Listening to Child Components Events

There are some features in a component that require communicating back up to the parent. For instance, we can support a feature by adding a postFontSize data property in the parent:

new Vue({
  el: '#blog-posts-events-demo',
  data: {
    posts: [/* ... */],
    postFontSize: 1
  }
})

This can then be used in the template to control the blog posts font size:

<div id="blog-posts-events-demo">
  <div :style="{ fontSize: postFontSize + 'em' }">
   <blog-post
      v-for="post in posts"
      v-bind:key="post.id"
      v-bind:post="post"
    ></blog-post>
  </div>
</div>

We can then add a button to enlarge the text before the content of every post:

Vue.component('blog-post', {
  props: ['post'],
  template: `
    <div class="blog-post">
      <h3>{{ post.title }}</h3>
      <button>
        Enlarge text
      </button>
      <div v-html="post.content"></div>
    </div>
})

The button currently does not do anything, if we want to get it to something on the parent we use the v-on directive:

<blog-post
  ...
  v-on:enlarge-text="postFontSize += 0.1"
></blog-post>

For the child component to emit an event we use the built-in $emit method and passing the name of the element:

<button v-on:click="$emit('enlarge-text')">
  Enlarge text
</button>

Emitting a Value With an Event

It is useful to sometimes emit a specific value with an event: we can pass the value as a 2nd parameter to $emit.

<button v-on:click="$emit('enlarge-text', 0.1)">
  Enlarge text
</button>

And then whenever we listen to events in the parent, we can access the emitted events value using $event:

<blog-post
  ...  v-on:enlarge-text="postFontSize += $event"
></blog-post>

Using v-model on Components

Whenever we use v-model on components it does this:

<custom-input
  v-bind:value="searchText"
  v-on:input="searchText = $event"
></custom-input>

Requirements for this to work is that the <input> inside the component must:

  • Bind to a value prop the value attribute
  • On input, it emits its own custom input event with the new value

A case scenario:

Vue.component('custom-input', {
  props: ['value'],
  template: 
    <input
      v-bind:value="value"
      v-on:input="$emit('input', $event.target.value)"
    >
})

v-model should now work perfectly with this component:

<custom-input v-model="searchText"></custom-input>

Content Distribution with Slots

<slot> makes it very simple to pass content to a component:

Vue.component('alert-box', {
  template: 
    <div class="demo-alert-box">
      <strong>Error!</strong>
      <slot></slot>
    </div>
})

Dynamic Components

Vue <component> provides an is attribute that enables us to dynamically switch between components:

<component v-bind:is="currentTabComponent"></component>

DOM Template Parsing Caveats

HTML elements such as <ol>, <ul>, <table> and <select> has restrictions on the elements that can appear inside.

But the is special attribute helps overcome these restrictions like so:

<table>
  <tr is="blog-post-row"></tr>
</table>