Exploring VueJS: Creating Components with Vue blog post

Exploring VueJS: Creating Components with Vue

Maya Shavin

18 Oct, 2019

Vue

13 minutes read

Welcome to the second part of our Exploring VueJS series! In this post, we continue diving into the next important topic of Vue.js – the Vue component, and how to build your first component in our Pokemon app, the Pokemon Card.

Before we start, all the code used in this series can be found here

Ready? Here we go.

The Vue class

All the components in a Vue app are reusable instances of a base class – Vue, with lots of prototype methods. So yes, that includes your app too.

The App instance

Creating the App instance

In general, a Vue app should consist of only one root Vue instance. We can create the root instance using new Vue from an object of configurations. This configuration object is similar to other components’ setting, with a few exceptional root-specific options, such as el.

Once you've finished reading this post, do you want to learn even more?

javascript

Learn JavaScript for FREE!

We've created the best online course for you to fully master JavaScript using real-world examples and techniques you’ll find nowhere else.

  • ES2015 and beyond
  • Variables and Scoping
  • Strings, Numbers and Booleans
  • Conditional Statements
  • Operators
  • Modules
  • Arrays and Objects
  • Functions and Closures
  • Loops and Iteration
Get course Get javascript Basics
/* main.js */
import App from './App.vue';

new Vue({

 router, //routing management - we will ignore for now

 store, //state management - we will ignore for now

 render: h => h(App),

})
.$mount('#app');

in which, we use render function to build an HTML view of the instance based on App – the root component options.

Mounting the App instance

Upon the creation of the instance, $mount() will be called explicitly with a unique selector string. This selector refers to an HTML element, where the Vue compiler will mount the app component onto during compilation.

Note: Alternatively, we can also mount the instance by passing the HTML element selector to its root-specific el option. Using this option will trigger the compilation immediately after creation.

So the code above can be re-written as:

/* main.js */
new Vue({

 el: '#app', //mount immediately after being created

 router, //routing management - we ignore for now

 store, //state management - we ignore for now

 render: h => h(App),
});

However, $mount() is usually preferred over using el, in the following cases:

  1. The chosen element does not exist at the time when the instance is created. The most common use case is in combination with other legacy apps, where components are injected into the DOM on runtime.

  2. There is a need to wait for some async processes to complete.

Component tree structure

Components are reusable Vue instances with names. We use them as a way to encapsulate functionalities within the app, and certainly as HTML custom elements. Hence our Pokemon app’s component structure will look like:

Pokemon App basic structure

As seen, all the components used within the app will be nested under the root instance of the app’s component tree, to maintain the single source of truth order.

Following that, our Vue app also embraces the single-file component concept for organizing our codebase.

Single-file component

As in our first post, our Vue app architecture follows strictly Single file component approach, which means a .vue file will represent a Vue component.

Basically a .vue file consists of three parts:

  • The <template> (HTML syntax) that will decide UI view of the component.

  • The (JavaScript) defines the options for creating component instance, following JavaScript Module syntax export default.

  • The defines stylesheet code for making the UI great.

In short, within a Vue component:

  1. Data will flow from the instance options defined in to and
  2. The instance will receive events emitted from the to perform necessary data updates.

Easy peasy, right?

Let’s create our first component – the Pokemon Card as a new file PokemonCard.vue under components folder and add the following code:



 


 /* Component's options as JavaScript module */
 export default {
 //options
 }

<style>
 /* Styling */
</style>

So far so good? Next, we’ll take a look at the options object – the one that defines the desired behavior of a Vue component.

The options for creating an instance

There are a lot of options you can choose to configure your component’s behavior. In this post, we will focus on the most basics one, which is: data, methods, and lifecycle hooks.

Data (or local state management)

First and foremost, data is local, which means it is accessible only within the component. data can be either Object or Function. However, in component definition (.vue file or Vue.extend()), data option has to be a function which returns a data object. This is to ensure each instance will have its own independent scoped data.

All properties defined in the returned data object are connected to Vue reactive system. Any change made to these properties will trigger updates to the relevant UI view.

/* PokemonCard.vue */

data: function() {
 return {
 title: "Sample pokemon"
 }
}

//is same as
data() {
 return {
 title: "Sample pokemon"
 }
}

For our Pokemon card, we will define the following properties:

  1. title – Name of the pokemon to display
  2. image – Photo URL of the pokemon
/* PokemonCard.js */
//in  tag
export default {
 data() {
 return {
 title: "Sample pokemon",
 image: "https://res.cloudinary.com/mayashavin/image/upload/v1562279388/pokemons/br5xigetaqosvwf1r1eb.jpg",
 }
 }
}

All properties defined in data returned object can be accessed directly using this, such as this.title or this.image.

Important notes: Any data property you wish to be reactive, it needs to be defined within data scope.

For example:

export default {
 data() {
 return {
 title: 'Sample pokemon'
 }
 }
};

PokemonCard.title = "Pikachu"; //Trigger the update on UI
PokemonCard.type = "Fire"; //NOT trigger update on UI

The reactive system, upon the creation of the component instance, will inject its tracking system onto each property found in the data object, enabling the binding between view and data.

Since the tracking mechanism will be deep-injected to every property of data object, it applies to all nested properties. That can cause unnecessary performance overhead, similar to cloneDeep.

Therefore it’s recommended to keep data object as flat as possible.

In case you wish to make an object un-reactive, it’s possible to do it using Object.freeze().

Methods

This is where we define all the methods/functionalities that will be accessible to the view.

Take our Pokemon Card, for instance, we will define toggleSelect to mark/unmark the card as selected upon the user’s click.



export default {
 data() {
 return {
 title: "Sample pokemon",
 image: "https://res.cloudinary.com/mayashavin/image/upload/v1562279388/pokemons/br5xigetaqosvwf1r1eb.jpg",
 //Flag property to keep track if component is selected
 isSelected: false 
 }
 },
 methods: {
 toggleSelect() {
 this.isSelected = !this.isSelected;
 }
 }
};

Lifecycle hooks

Each Vue instance lives and dies within a lifecycle. And along that cycle, it will trigger lifecycle hooks functions synchronously, which allows users to inject their code at specific stages.

In general, the lifecycle of a Vue instance can be presented in the following stages:

  1. Set up events and reactivity system for data (beforeCreated will be triggered)
  2. Compile and create the template with binding with the reactivity system (created will be triggered)
  3. Mounting the instance to DOM (beforeMount and mounted hook will be triggered respectively)
  4. Listen and update the DOM whenever data changes. (beforeUpdate and updated will be triggered respectively)
  5. Remove all listeners and bound reactivity (beforeDestroy and destroyed will be triggered respectively)

We will come back to discuss more on the lifecycle in later posts. Meanwhile, you can refer to the diagram below for a brief understanding.

Vuejs component lifecycle diagram

Great. We’re almost done with our first component. Here comes the next question, how do we bind our properties to the view template?

Data binding and template

As said above, all Vue instance’s data is connected to the rendered DOM through its reactivity system. And templates are simply valid HTML-based syntax that will allow us to enable the binding between the rendered DOM and the underlying Vue instance’s data.

Mustache syntax

Vue.js supports text interpolation using double curly braces {{ }} (“Mustache” syntax). And we can write the relevant property expression as is, without the need of this on the template section.

<h3>{{title}}</h3>

During compilation, the mustache tag {{title}} will be replaced with the relevant value of title in our data object. And indeed, this tag field will be updated during run-time whenever the value of title changes.

However, we can’t apply the same approach with to set image property as source src of <img/>. Mustache syntax doesn’t work with HTML attributes. Instead, we use v-bind directive.

v-bind directive

v-bind directive helps to bind attribute(s) or a component prop to a data property expression. We can declare the binding with v-bind:[attribute] syntax or :[attribute] as its shorthand. Hence our img tag will be:

<img :src="image" />

And that’s it. The image of our Pokemon Card is bound and ready.

What about attaching our toggleSelect method to click event listener?

v-on directive

v-on and v-bind are the two most basic and frequent use in every Vue application. While v-bind only binds an attribute (or a prop) to data property, v-on attaches an event listener to the element, and ties that event to an available method or inline statement(s):

v-on:[event_type]="[accessible method | inline statement(s)]"

//OR

@:[event_type]="[accessible method | inline statement(s)]"

Which in our Pokemon Card, it will be

Notes: v-on can only listen to native DOM events when used on normal HTML elements. In this case, the method will receive the native event as the only argument and will have access to $event if using inline statement(s):

Besides, there are several event modifiers can be used to help to release the method logic from handling the DOM event details itself. These modifiers are directive postfixes denoted by a dot, which is equal to:

@[event-type].[event-modifer] = "[method | inline statement(s)]"

//Example:
//@click.stop=""toggleSelect"

Take event.stopPropagation() for an instance. Instead of writing:

toggleSelect(event) {
 event.stopPropagation();
 this.isSelected = !this.isSelected;
}

we can re-write as following:

toggleSelect() {
 this.isSelected = !this.isSelected;
}

And hence our code will be completely independent and reusable outside of the DOM event handling scope.

The full list of modifiers can be found in Vuejs documentation. Just for a quick look, there are some common modifiers that is worth to remember, such as:

  • .stop –> event.stopPropagation().
  • .prevent –> event.preventDefault().
  • .once – this will trigger handler at most once.

With these basic data-binding concepts, our final template now will be:


 

{{title}}

Selected: {{isSelected}}

</template>

And that’s it! Our component is ready. But wait one moment, we still can’t see it on the UI yet. Why? Because we still need to register it to the compiler.

Registering your component

To start using your component in a Vue app, you need to register it, for the compiler to retrieve the required information.

You can register components via 2 ways:

  1. Locally – Register to a specific component as its sub-components by adding it to components and rename it if needed. The registered component will be scoped-available for use only by the component is registered to. For example, we want to register PokemonCard as local components for using inside Home component (which is located under the views folder):
/* Home.vue */
import PokemonCard from '../components/PokemonCard.vue'

export default {

 //...

 components: { pokemon: PokemonCard }

}

//same as

export default {

 //...

 components: { PokemonCard } //take original name

}

Or

  1. Use Vue.component() to make your components available globally within the app. Also, you can even rename the element for your needs.
/* main.js */
import PokemonCard from './components/PokemonCard.vue';

Vue.component('pokemon-card', PokemonCard) //kebab-case

//Or
Vue.component('PokemonCard', PokemonCard) //PascalCase

Note: For good practice, it’s recommended to have components defined with the PascalCase naming convention, since you can use either case when referencing its custom element in the HTML template. Which means


<pokemon-card/> //OK

<!-- Or -->

<PokemonCard/>//OK

will be the same.

However, if you wish to use it directly in the DOM, only kebab-case names such as pokemon-card will be valid.

Notes: In this series, we will register all components locally, to keep the app architecture as scoped and organized as possible.

Finally, let’s review our code of today’s post so far.

Styling in component

At this line, PokemonCard.vue code will be:

<!-- PokemonCard.vue -->
<template>
 

{{title}}

Selected: {{isSelected}}

</template> export default { data() { return { title: "Sample pokemon", image: "https://res.cloudinary.com/mayashavin/image/upload/v1562279388/pokemons/br5xigetaqosvwf1r1eb.jpg", //Flag to keep track if component is selected isSelected: false } }, methods: { toggleSelect() { this.isSelected = !this.isSelected; } } }; <style> </style>

Meanwhile in views/Home.vue:

<!-- Home.vue -->
<template>
 
</template> // @ is an alias to /src import PokemonCard from '@/components/PokemonCard.vue'; export default { name: 'home', components: { PokemonCard, }, };

And if you haven’t started local dev environment, simply run the following command on the terminal:

yarn serve

to start compiling and get the app served locally at https://localhost:8080 as:

Pokemon App Screenshot - Without styling

So far so good, right? But maybe a tiny touch 💅 to make the PokemonCard looks like a real card, shall we?

Let’s head to the section style inside PokemonCard.vue and do the following:

Make it scoped styling

scoped is absolutely a must-do for keeping the CSS styling applied only within the component. This is great to ensure our control over styling across the entire application.

Use lang with pre-processor

This is optional. If you feel more comfortable writing pure CSS, or less, or stylus, go ahead. It’s entirely up to you. But in case you don’t write pure CSS, remember to add lang="[pre-processor]" inside style tag. And here we go:

<!-- PokemonCard.vue -->
<style scoped lang="scss">
.card {
 border: 1px solid lightgray;
 padding: 5px;
 margin-right:10px;
 margin-bottom: 10px;
 width: 220px;
 box-shadow: 0 1px 2px rgba(0,0,0,.2);

 &:hover {
 background-color: #d3d3d363;
 cursor: pointer;
 }
}
</style>
Pokemon App Screenshot - With styling

So how do it look right now on the browser?

Awesome! Much more beautiful, isn’t it? Congrats, you just got your first component created 🎉.

Conclusion

At this point, we have gone through the concept of Vue class, how the App instance works, and how to create a reactive component step-by-step, with a detailed explanation on basic options for that component. Moreover, we also learned about how to use the created component, globally and locally within the application.

Of course, our code is available for try out here

What’s next?

Since we only cover the basic configuration options such as data and methods, every PokemonCard component instance has the same data information value. What about computing different information to display based on the existing data properties of the component? In the next post, we will cover reactive dependencies and how data-binding using computed, watch property are done in Vue.js.

Until then, thank you so much for reading, stay tuned, and see you in the next post 🚀!

About the author

Maya is a Senior Frontend Developer @Cloudinary, founder and organiser of VueJS Israel, core maintainer of StorefrontUI and blogger on Frontend Weekly and Ultimate Courses where she mainly writes articles about JavaScript tutorials and good practices. She has been developing web apps with Angular, VueJS and recently ReactJS. She loves to learn and experiment with new framework, while believing that a strong Vanilla JavaScript knowledge is necessary for being a good web developer. When not coding, she enjoys traveling, reading manga, and sketching.

Love the post? Share it!

Lots of time and effort go into all our blogs, resources and demos,
we'd love it if you'd spare a moment to share them!

Explore our Angular courses

Get started today and join over 60,000 developers.