In the past two weeks I invested a considerable amount of time on learning the Vue Javascript framework. This post is to document what I've learned so far, and to provide a gentle and approachable introduction to the framework for those interested in learning it as well.
Although I'm still far from mastering it I believe that I now have a good enough grasp of the core concepts that make Vue an excellent framework for creating involved user interfaces. This article is therefore split into two parts - firstly, an explanation of Vue is from a theoretical point of view, followed by a gentle introductory example in which we'll build a simple app to highlight some of Vue's features and how they work together.
The only requirements to follow along with this post are some familiarity with HTML and JS, and how they work together to create the behavior behind webpages.
What is Vue
Vue is a Javascript framework aimed at simplifying the creation of user complex interfaces. When doing things with vanilla JS things can often quickly get out of hand, and you'll end up with bloated code that is not maintainable. How many times to you need to fetch something by it's DOM id when you simply want to update a value displayed on your page? Well, no more, because Vue is intended to be the silver bullet to this imperative manner of doing things.
Vue takes a declarative approach to doing things. This means, that instead of manipulating the DOM directly, we actually just tell Vue what we want to do, and it takes care of the logic for us. Vue is also reactive, and will take care of passing around values between different elements in our interface, depending on how we wire things up.
This sounds too good to be true, however, this way of writing code comes with it's own set of challenges. For me personally, I found it a bit tricky to get used to writing code in that manner, but once you get the hang of it, it should be a walk in the park.
I don't want to waste too much time telling you what Vue is, and would much rather show you what Vue is. How it works, and how it improves on implementing things with vanilla JS. You'll understand what Vue is along the way.
Good ol' Imperative Javascript
Let's say we want to make a very simple single page web app: a single button that tracks how many times it's been clicked. How would we do this with vanilla Javascript? Here's what this would look like:
Let's start with the minimal html:
<body>
<div id="clickCounter">
<button id="countBtn">
Count is: <span id="countDisplay">0</span>
</button>
</div>
<script>
// javascript code goes here
</script>
</body>
And the Javascript would be:
document.addEventListener('DOMContentLoaded', function() {
var count = 0;
var countBtn = document.getElementById('countBtn');
var countDisplay = document.getElementById('countDisplay');
countBtn.addEventListener('click', function() {
count++;
countDisplay.textContent = count;
});
});
Luckily there's event listeners in Javascript that allow us to create this behaviour. Here we simply fetch the button element by it's id from the DOM, after the DOM has loaded, and then attach the event listener to it such that it's triggered when the button is clicked. When this happens the variable counter is incremented, and is assigned to the textContent of the button variable.
Hmmm, okay... that works, but there's a few things that we need to address. That's already quite a lot of code for something very basic, that doesn't even do much. Besides, we also have our HTML and Javascript in two separate places: which means, if we were to make a change to one of the two, we'd also have to update the html/javascript counter piece. The more complex the interface, the longer the code, the more tedious making changes will become.
Let's try to recreate this exact example with Vue and see how the framework streamlines the creation of interactive elements in the page. After considering a couple of different examples, for instance a login form, I decided on borrowing this example from vuejs.org, with some modifications. It's simply the shortest and most digestible example to give a preliminary showcase of vue.
On the order of things
The biggest challenge of writing this article was deciding on the ordering of how to present the different vue concepts. Vue features are quite intertwined, hence as a beginner it's tricky to immediately get a complete picture of things when you get exposed to a chunk of foreign code right off the bat. We'll try to do things in an incremental and hands on manner.
It's also important to note here that there's multiple different syntactical ways to putting together the code for a vue app - I'll try to stick to the most straighforward manner of doing things, the one that 'clicks' for me at least.
Also noteworthy here - we'll be working with Vue 3, which works quite a bit differently from Vue 2. I'm not certain however if these differences matter at a beginner level however.
Vue Components
Ok, enough foreplay - let's get straight into it. In this second part we'll create our first vue app from scratch, using the previous vanilla javascript example as a starting point.
The main ingredient of creating vue apps are components - you can think of them as individual building blocks that allow us to create the different parts of a complex UI. These components usually represent the individual elements in our web page's DOM.
Moreover, vue components are reusable and self-contained units of code that encapsulate the functionality, structure, and styling of specific parts of a user interface. Organizing these components in a parent-child-like relationship, allows us to model intricate interactions between different parts of the interface and have them communicate with each other in a meaningful manner. For instance, several smaller components can make up more complex, larger components.
The two main advantages of this modular approach are, firstly, a reactive UI where data and functionality is encapsulated within the different individual components, and secondly, an underlying codebase that isn't an atrocious spaghetti mess, but rather something that is much more manageable - in contrast to the vanilla js approach where things can get hairy really quickly. Both of there advantages stem from vue's declarative manner of doing things. What this means, will become clearer throughout the coming sections.
Even though Vue is one of the easier frameworks to learn, it still has some concepts that require a little bit of getting used to. At some points it might seem like it's its own programming language. Let me show you the ropes now, and it shouldn't be too difficult to get a grasp of what such a component operates.
Our first vue app
Creating a vue app boils down to three lines of code:
- Making an HTML container that will serve as a container for our vue app
<div id="app"></div>
- Creating the app in a script tag/file
let app = Vue.createApp({});
- Mounting the vue app that we just created into it's designated container
app.mount('#app')
With this knowledge let's recreate our clicker counter with vue. This time the actual HTML in the body of our page will be much simpler - actually, we're going to leave it empty for now; there's a better way to create the HTML button with Vue. Besides the container into which we'll mount our app, we also need a script tag where we'll place all of our Vue code:
<div id="app"></div>
<script>
// vue code goes here
</script>
And the Vue code to create the app:
let app = Vue.createApp({})
app.mount('#app')
The mount() function simply attaches the Vue app to the DOM such that it renders when we access the page. Okay, in the next sections we can actually start writing some Vue code!
Root component and Vue Template Syntax
How do we create our HTML button now?
As you might have noticed, the createApp() function actually takes an input parameter. Here we simply passed in an empty object, but in reality this is the root component of our Vue app, that serves as the top level component and entry point of the entire application.
In what follows, we'll see that Vue components are mainly modeled as JS objects, that encapsulate certain, Vue specific, options (object properties basically) that define these components' appearance and functionality. In this case passing in an empty object didn't introduce any appearance nor functionality. So nothing will actually render yet. We'll also get acquainted with a few of these options in the coming sections.
At the very top of these component objects you'll most often see a property called template, which will contain a string consisting of HTML code that represents the corresponding DOM element of a particular component. It's essentially a blueprint for the component's HTML that will be rendered into the DOM where we place the component.
For instance, in our case:
vue.createApp({
template: `
<button>
Count is:
</button>`
`
})
Now this would actually render the template option of our root component into the div with the id app.
What's important to note here is that we generally use backticks to define this template string, as HTML itself sometimes contains single and double quotes. For example, if we assign a class or id to as tag attributes. While using backticks we don't have to worry about escaping these symbols.
The backticks also come in handy, because we'll be using some of Vue's template specific statements. These can be used to inject values or functionality into our template string.
Another thing worth mentioning here, is that this template code can be encapsulated as a separate file called SFC, short for Single File Component. This becomes useful when the template code becomes lengthy. In that case, for clarity's sake it makes more sense to store it separately.
Reactive Data and Component Methods
This is a good point to introduce two other, frequently used Vue component options. The first one is known as the data function and is declared in the following manner, usually right after the template option:
let app = Vue.createApp({
template: `
<button>
Count is: {{ count }}
</button>`,
data: function(){
return {
count: 0
}
}
})
The data function should always return an object. This object is essentially a representation of the internal state of the component. For instance, our clicker counter button should be able to remember the number of times that it has been clicked over the course of it's life time. The best way to do this is by storing this click count as a property in the returned data object.
Now you'll also notice that we made a change to our template HTML - we added the count variable inside of two pairs of nested curly braces. It's also known as handlebar syntax, or alternatively mustache syntax, if you need to google it at some point. What is does, is that it'll automatically convert the double curly brace statement into the value that the count property of the data object contains - which has an initial value of 0. We mainly use this syntax for accessing the values from properties return by the data function, but there's some other Vue object options for which we can use it as well.
What's special about this data object, is that it's reactive. Meaning that, if the count variable is incremented, decremented, or changed is some way or another, it'll be automatically reflected in the HTML without us having to imperatively access the element in the DOM and update it. That's super convenient in so many scenarios, right?
Next we'll tackle how to increment our count variable whenever the button is clicked. We'll also do this in a self contained manner within our component, without having to fetch the button element by it's id.
Queue the methods option:
let app = Vue.createApp({
template: `
<button @click="incrementCount">
Count is: {{ count }}
</button>`,
data: function(){
return {
count: 0
}
},
methods: {
incrementCount: function(){
this.count++
}
}
})
The methods option essentially represents the functionality of a component.
This option is in essence an object that contains functions as properties, which can be used in our component to perform computations or add behavior, like updating the template HTML, upon certain events. Just like the code that gets executed during regular vanilla event listeners.
In our case, we'd like to increment the count property when the button is clicked. Hence we'll create a function that does just that. Notice how we also need to reference the count variable with the this keyword. In an OOP flavored manner we need to point Vue towards the correct instance of the variable that lives within the component object.
How do we actually trigger this method when the component is clicked? We'll have to tell the button element to listen for a click. With Vue we can create this event listener with the v-on directive.
A Vue directive is kind of just like an HTML attribute, in that it gets added to an HTML element in it's opening tag, but with the special behaviour that it gets parsed by Vue instead of being rendered to the DOM. To listen to a click event, we simply add this statement @click=incrementCount. This simply triggers the incrementCount method when the button is clicked (the @ symbol here is a shorthand for the v-on directive). We'll talk in more detail about Vue directives in the next section, but for this part I simply want to show you the syntax.
To put all of what we covered so far into action - we get a relatively unimpressive looking click counter button:
Inspecting the HTML in this case doesn't actually do much, as Vue will automatically convert the code to the final HTML - viewing the page source however, should make it possible to see what's happening underneath.
Vue Directives
To conclude this quick speed-run of Vue basics, let's have a look at directives! As mentioned above, they're essentially a way to extend HTML beyond it's usual functionality. They can be used to add specific behavior and functionality to our components in a declarative manner, and in a way mimicking HTML tag attributes.
We've already seen the @ symbol, which is actually just a shorthand for the v-on directive, that can be used to attach a click event listener to our DOM elements. We could have equivalently written it like this:
<button v-on:click="incrementCount"></button>
Where the click event is already built into Vue as a click event listener. We can naturally replace the click statement with our own custom events - but that's a tag bit more complicated, so we'll leave it for another time.
Another directive that we could have used in our button is the v-text directive, which is a convenient way to set the HTML content of a div. For example, instead of using Vue's handlebar syntax for our button we can equivalently do it in this manner:
<button @click="incrementCount">
Count is: <div v-text="count"></div>
</button>
The count variable is inserted into the component template via the v-text directive. I believe that having multiple ways of doing things in vue adds a little to it's complexity but also makes it more flexible in many ways.
And yet another directive is the v-show directive which can be used to show directives based on certain conditions.
<button @click="incrementCount">
Count is: <span v-text="count"></span> <span v-show="count != 0">times clicked</span>
</button>
So this is really cool, and highlights another cool thing that can be done in the Vue, evaluating expressions directly in the template!
There's many more directives that can be used for all sorts of purposes. I believe that the added declarative functionality from these directives is one of the hardest parts to master in Vue, but also makes many things much more straightforward.
It'll take a little bit of time to figure out how to use all of these directives effectively, but with a little practice it shouldn't be too difficult. At this point I recommend recreating this small vue app by yourself, if you feel so inclined, it'll help understanding what follows where we'll create a more complicated app!
Towards a more intricate Interface
Most interfaces have more than just a single button, and often require different parts of the interface to communicate with each other in some manner. This is the aim of this section. We'll reuse the button that we've put together over the previous sections and will create an interface that holds several of these buttons. For this purpose we'll reorganize our code a little bit and learn how to create a more intricate interface with several components.
Chaining Components
At this point in time, we've created a self contained clicker counter component that sits directly at the root of our app. How do we go about making this button reusable as a Vue component? What's the Vue way of doing things?
The first step is to clear up our root component, and turn it into a container for the interface that we're planning on building. Let's set the button component code aside for a second. Instead of having it at the root, we'll start off with something that looks like this:
let app = Vue.createApp({
template: `<buttons/>`,
components: ['buttons']
})
app.component('buttons', {
template: `<div>Here go the buttons.</div>`
})
Alright, there's already a lot to unpack here. Let's focus on the root component object that is fed to vue's createApp() function for a second. The template for the root component's HTML contains a buttons tag... if you're acquainted with HTML, you'll notice that this isn't actually a valid tag that exists in HTML, as opposed to the singular form of the word, button, that is.
Last I heard, you couldn't just invent new HTML tags. Well, Vue actually lets us create our own tags! Let me explain how this works!
In what preceded we talked a lot about components, but we haven't actually created any so far. Well, save the one that needs to sit at the root of the app. There's multiple ways to create components in Vue, the most straightforward being via the app.component() method. In contrast to the createApp() function, this one takes two inputs: a label/name for the component, as well as an object that specifies the component's options - which we've already discussed extensively at this point.
Besides the top level container at the root of our interface, we've also created a component that will serve as a container for multiple buttons:
Here we're creating a new component called buttons, which will simply be a container that holds several buttons (we'll add these in a just a minute).
For now this component has a very bare-bone template, simply a div with some text in it. By having created a component with the name buttons, Vue actually allows us to use this identifier as an HTML tag. Using the component's name as a tag inside of the root component's template, it'll conveniently and automatically replace this custom tag with the HTML inside of the component's template.
In simpler terms: Vue components can be used like custom HTML tags. Using such a custom tag in the template of another component, replaces it with the content of it's own template string.
This was a bit of a mouthful to explain, but I hope it makes sense.
Additionally, we can also give the root component an option called components which indicates what child components we want to use inside of it:
app.component()
This isn't necessary in our case, Vue would still automatically understand what the buttons tag is referring to, but it's good practice nonetheless and might be required sometimes.
Alright, now we've seen another super powerful feature of Vue - let's make use of it for creating multiple buttons!
Multiple Buttons
Now let's create a new component and feed it the button component object that we've created earlier:
app.component('clickerButton', {
template: `
<button @click="incrementCount">
Count is: {{ count }}
</button>`,
data: function(){
return {
count: 0
}
},
methods: {
incrementCount: function(){
this.count++
}
}
})
The only caveat here is that we should not give components names that are identical to already existing HTML tags. That would cause problems, hence, instead of using the label button for our component, we'll name our component clickerButton. Yes, I know, very imaginative.
We can now repeat the same process as before and insert our custom button as an HTML tag inside of our buttons container component:
app.component('buttons', {
template: `<button />`
})
In this manner vue will automatically convert this tag to our custom button. Now what if we want to create two buttons? We could simply insert the tag twice. What if we want to insert the button n times? We could simply insert the tag n times... but that's hardly ever a good way to do things in code.
Vue got us covered in this case well, and we'll simply use another directive, namely the v-for directive:
app.component('buttons', {
template: `<button v-for="i in 5">`
})
As the name suggests, the v-for directive is basically a convenient for loop that lets us generate multiple elements at once. For instance, in the previous example we created 5 clickerButtons in it's parent container.
And here it is in action:
See how these buttons are completely independent from each other? When you click one, it'll automatically update it's counter without affecting the others. Which is really cool.
Parent to Child Component Communication
We've created a bunch of buttons, but they've got a bit of a sad existence - they don't actually know that they have siblings. How do we change our code, such that all buttons update their counters whenever any other button is clicked?
In this case it makes sense to have a single global counter that lives inside of the buttons container and that is passed to the clickerButton child components. This counter should then be updated whenever any of the buttons is clicked. Let's get to work.
To pass a value from a parent component to a child component we need to use Vue props. It's an abbreviation for the term properties, and is an option that can be added to a component when we expect it to receive a value from a parent component. So our clickerButton component becomes:
app.component('clickerButton', {
template: `
<button @click="incrementCount">
Count is: {{ count }}
</button>`,
methods: {
incrementCount: function(){
// need to do something different here now
}
},
props: ["count"]
})
Here we don't actually require the data option anymore since we are receiving the count variable as a prop from the parent. The buttons component also needs to change, to send a variable downstream to a child component we can use the v-bind directive, which essentially binds a data property to a child element:
app.component('buttons', {
template: `<clickerButton v-for="n in 5" :count="globalCounter"/>`,
data: function(){
return {
globalCounter: 0
}
}
})
It's called v-bind because this is done in a reactive manner, when the globalCounter of the parent is updated, an updated value will also be passed down to it's children.
Child to Parent Component Communication
Now that we've set up our clickerButtons to receive a value from the parent, we also need to send a message back to the parent when one of the buttons is clicked. For this we can set up another method, in which we'll call a special Vue function called $emit():
app.component('clickerButton', {
template: `
<button @click="incrementCount">
Count is: {{ count }}
</button>`,
methods: {
incrementCount: function(){
console.log('hi')
this.$emit('counterUpdate')
}
},
props: ["count"]
})
The emit
function is a special method in Vue that allows us to send a message to the parent component, it's essentially like an event propagated to the parent. This emit function takes a string label and as second input what we call a payload - a value that is passed alongside the event label.
In the parent we now need to listen for this event, which can also be done with a a click event listener via the v-on directive and a corresponding method that triggers when this event is registered - not how the event to listen for is simply the name of the emitted message:
app.component('buttons', {
template: `<clickerButton v-for="n in 5" :count="globalCounter" @counterUpdate="handleChildEvent"/>`,
data: function(){
return {
globalCounter: 0
}
},
methods: {
handleChildEvent(payload) {
console.log(payload)
this.globalCounter++
}
}
})
And there we have it, in action this would behave like that:
Closing Thoughts
Although we've just scratched the surface of the Vue iceberg, I hope this article sheds some light on the framework and hopefully gets you started on your own Vue apps. It took me a couple of days to get a hang of Vue and how its features are intended to be used together. At the same time it was also a very fun challenge recreating one of my own vanilla JS concoctions with Vue. I think it's the best way to learn Vue.
We've covered the features of Vue quite broadly, and as mentioned there's always a multiplicity of different ways to do things - it really ultimately boils down to what you want to create and how it should behave. Although in certain cases there is a proper approach to things, you shouldn't get hung up on that when starting out. Vue is intended to be a progressive framework, and makes it easy fix things later on if there's a better way to do things.
One last note, I also found that chatGPT is quite potent at answering Vue related questions - for instance you can simply state what you'd like to accomplish, and it will give you some potential solutions on how to do it. For instance: 'In Vue, how do you emit a message from a child component to a parent component?' and it'll provide you a potential answer. It isn't always perfect, but it's good enough most of the time. It's especially useful when you don't really know what you're doing yet and would like some way-pointers on achieving particular goals.
That said, I hope you enjoyed reading this post as much as I enjoyed writing it! If you do end up giving Vue a shot after this, shoot me a message or tag me when you share your creation. Otherwise consider sharing this post on your socials, or with friends and family that have an interest in programming. It helps out a whole bunch. Cheers and Happy coding ~ Gorilla Sun 🌸