Learn VueJS by building a simple Todo-App.

Hugo Johnsson
12 min readMar 9, 2019

Purpose

This article is meant to teach you the basics of VueJS by building a Todo-App, it will be practical and comprehensible. My targeted audience is complete beginners when it comes to JavaScript frameworks but also developers who comes from other frameworks like React and Angular that want’s to learn Vue as well.

I hope you find this article helpful! Let’s get into it.

Installing VueJS

Installing Vue is as simple as typing in the following line in your terminal, if you don’t have Node installed (which is required to use the npm command), go to Nodejs.org and install the latest version.

$ npm install -g @vue/cli

This command will also install the VueJS Command Line Interface which we will be using for creating new projects etc.

Creating our project

We will now create our project by running the command down below, it will ask you to pick a preset, choose the default one.

vue create todo-app

Then move into your project and run the server to make sure everything works.

cd todo-appnpm run serve

This will start the server on port 8080, if you want you could go to localhost:8080 and see the VueJS welcome page.

About VueJS and folder structure

I’m not going to get into the details about how VueJS works but basically it’s a Javascript framework used to build Single Page Applications, I encourage you to read more about these types of applications.

Open the project folder and you will see a bunch of files as well as two folders. For this application we will only have to worry about the folder called “src”, you can ignore the other one as well the additional files located in the project root. But I will still give you a basic idea what these does.

The folder called “public” contains a html file, it’s this file that will serve our Vue App. Single page application works by loading up a main HTML file that have a container with an ID usually. It’s in this container the Vue application will be served.

The file called “package.json” will be familiar to you if you have used Node before, it’s purpose is to hold all our dependencies, the ones that is required to create Vue apps as well as packages we install ourselves for different reasons.

The “src” folder

This is the folder we will be working with today. If you have worked with other Javascript frameworks chances are that you have worked with so called components, VueJS also use this concept. Basically every part of the UI in our application is part of a component, these components can be nested, combined and be used in various different ways. When creating a VueJS application the way we did it, a main component called “App” is automaticaly created for you, you can look at the component in “App.vue”.

<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'app',
components: {
HelloWorld
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

The “App” component is the main component that will be loaded, essentially we will nest all of our “Todo App” components in this main component. You will see how we use this to construct our application soon.

If you look at the file called “main.js” you can see it being rendered to the DOM. You can see that it makes use of the element with the id of “app” in index.html.

import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = falsenew Vue({
render: h => h(App),
}).$mount('#app')

The file structure of a component

Before we start building our app I will take a moment to explain how you build your components.

The template tag: This will serve the HTML markup for our component.

<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>

The script tag: Here we import other components that we may want to use within our current component, we also define the components name.

<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'app',
components: {
HelloWorld
}
}
</script>

The style tag: The style tag as you may guess will serve our component styling, one thing worth mentioning before we move on is that the style you write here applies globally. In most cases you don’t want this and you make the styling apply only on the current component by adding the word “scoped” to the opening style tag.

<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
// To make styling only apply to current component:
<style scoped>
</style>

Let’s start building!

Now you know the basics of VueJS and are ready to start building the Todo-App.

Cleaning up the project

In “src/App.vue” I am going to remove everything that comes with creating a new Vue app so it looks like this:

<template>
<div id="app">
</div>
</template>
<script>export default {
name: 'app',
}
</script>
<style>
</style>

I’m also going to remove the image in “src/assets” as well as the file called “HelloWorld.vue” in “src/components” as we don’t need them. We are going to create new components for our app.

The data

In “App.vue”, make sure the script tag looks like this. This is how you store data in Vue components and as we need access to this array from everywhere in our application we are initializing it in the main component.

I also added some dummy data, so now we have an array of todo objects.

<script>
export default {
name: 'app',
data() {
return {
todos: [
{
id: 1,
title: 'Go workout',
completed: false
},
{
id: 2,
title: 'Do laundry',
completed: false
},
{
id: 3,
title: 'Cook food',
completed: false
},
{
id: 4,
title: 'Clean up room',
completed: false
},
{
i: 5,
title: 'Finish work',
completed: false
}
],
}
},
}
</script>

The components

We are now going to create our components. In “src/components”, create two files, I’m going to call them “Todos.vue” and “Todo.vue” but of course you can call them whatever you want. The “Todos” component is the container where all the todos should be displayed, and for every todo in our list the “Todo” component is used with it’s own unique data.

Todos.vue

<template>
<div>
<h2>My todolist</h2>
</div>
</template>
<script>
import Todo from './Todo';
export default {
name: 'Todos',
components: {
Todo
}
}
</script>
<style scoped>
</style>

Todo.vue

<template>
<div>
</div>
</template>
<script>
export default {
name: 'Todo',
}
</script>
<style scoped>
</style>

Let me explain what I am doing here, I have created two components, in “todos.vue” I’ve actually imported the “Todo” component to be able to use it for every todo in our todolist. You import components by adding a import statement right after the opening script tag and then in the “export default” add “components”, in “components” you add need to add all imported components to be able to use them. Notice that we are using the name we declared in the “Todo” components file when we are importing.

Now we also need to add the “Todos” component to “App.vue”. Here you see how we actually use components, I’ve imported the component and put in the div tag with the id of “app”.

App.vue

<template>
<div id="app">
<Todos />
</div>
</template>
<script>
import Todos from './components/Todos';
export default {
name: 'app',
components: {
Todos
},
data() {
return {
todos: [
{
id: 1,
title: 'Go workout',
completed: false
},
{
id: 2,
title: 'Do laundry',
completed: false
},
{
id: 3,
title: 'Cook food',
completed: false
},
{
id: 4,
title: 'Clean up room',
completed: false
},
{
i: 5,
title: 'Finish work',
completed: false
}
],
}
},
}
</script>
<style>
</style>

If you go to localhost:8080 after you added this “My todolist” should be displayed as I added a <h2> tag in the “Todos” component.

Displaying our todos

It’s time to display our todos. To be able to use our data in the “Todos” component we somehow need to pass it down from the main component.

In “app.vue” the “Todos” component should look like this:

<Todos v-bind:todos="todos"/>

What we’re doing here is passing the todos to the component as properties, but we refer to this as “props”.

We need to do one more thing, in “Todos.vue” your script tag should look like this:

<script>
import Todo from './Todo';
export default {
name: 'Todos',
components: {
Todo
},
props: [
"todos"
]
}
</script>

Note that I’ve added “props”, this is an array where we put all the properties we want to use, in “app.vue” we passed down our todos as “todos” so that’s the name we use in “Todos.vue”.

Now that we have our data available we need to display it, remember that our data is an array, that means we need to loop through them.

Todos.vue

<<template>
<div>
<h2>My todolist</h2>
<ul>
<li v-bind:key="todo.id" v-for="todo in todos">
<Todo v-bind:todo="todo" />
</li>
</ul>
</div>
</template>

You can see that I’ve added an unordered list, then I added a <li> element but with the “v-for” added to it. This is how we loop through array in the HTML markup, it means that we for every todo in our todos array an <li> element is going to be rendered. In every <li> element that will be rendered I use the “Todo” component we created.

If you’re familiar with React we always need to add a unique key to every item when we loop through arrays. Vue also requires this and in our case we do this by adding the line: v-bind:key=”todo.id”, we make use of the todo objects id.

In the “Todo” component that is being rendered for every item, we pass the current todo object exactly the same way we passed down our todos from the main components. We can now access this todo object in our “Todo” component.

Todo.vue

<template>
<div>
<p>{{ todo.title }}</p>
</div>
</template>
<script>
export default {
name: 'Todo',
props: [
"todo"
]
}
</script>
<style scoped>
</style>

If you go to localhost:8080 you should now be able to see all of our todos displayed in an unordered list.

Adding functionality

Right now we are only displaying our todos, but it would be much of a todo list if we can’t add new todos, cross them of as well as delete them.

Let’s fix this.

We are going to start by adding the functionality to add todos. Before we move on make sure you run this line in the root of your project:

npm install uuid

This is going to install a package that let us generate a random id for every new todo that is being created.

After you’ve done with installing uuid, create a new file called “AddTodo.vue” in “src/components”.

AddTodo.vue

<template>
<div>
<form @submit="addTodo">
<input type="text" v-model="title" name="title">
<button type="submit">Add</button>
</form>
</div>
</template>
<script>
import uuid from 'uuid';
export default {
name: 'AddTodo',
data() {
return {
title: ''
}
},
methods: {
addTodo(e) {
e.preventDefault();
const newTodoObj = {
id: uuid.v4(),
title: this.title,
completed: false
}
this.$emit('add-todo', newTodoObj);
this.title = '';
}
}
}
</script>
<style scoped>
</style>

This probably look very confusing to you, but don’t worry, I will explain it all.

So in our script tag we added some data, the data contains a title, this title are going bound to our input in our form. This is a common practice in Javascript frameworks, it’s commonly refered to as “two-way binding”. All it does is that as soon we make a change to our input the “title” in our data is also updated, this allows us to use the value of “title” when want to add todos. If you look at the input in our form it looks like this:

<input type="text" v-model="title" name="title">

We use “v-model” bind in our case the input to our component data.

In the script tag we have also added a method called “addTodo”, this method is going to be called whenever we submit our form.

Here you can see how we bind the method to the form submit event:

<form @submit="addTodo">
<input type="text" v-model="title" name="title">
<button type="submit">Add</button>
</form>

We use the “@submit” tag.

The addTodo method

addTodo(e) {
e.preventDefault();
const newTodoObj = {
id: uuid.v4(),
title: this.title,
completed: false
}
this.$emit('add-todo', newTodoObj);
this.title = '';
}

First of we use the event and basic Javascript to prevent the form trying to submit to a file. Then we create the new todo object which contains an unique id, a title (“this.title” refers to our data that is bound to our input) and a completed property.

After we create our object we are emitting an event called “add-todo”, we are going to define this event in our main component soon.

So what is an event you may ask? Well, an event is bound to a function, in our case the event “add-todo” is bound to a function in our main component that takes an todo object as argument. Before we move on to creating the event I also want to mention that after we emit the event we clear our “title” field in our data, as this is bound to our input the input is going to be cleared.

The add-todo event

First we import the “AddTodo” component and put it under “Todos”. By now you know how to do that so try to do it yourself.

app.vue

<template>
<div id="app">
<Todos v-bind:todos="todos"/>
<AddTodo v-on:add-todo="addTodo"/>
</div>
</template>

If you look at the <AddTodo/> we have added a “v-on” tag, this is how you define events, it says that on the “add-todo” event call the “addTodo” function which we define in our script tag.

<script>
import Todos from './components/Todos';
import AddTodo from './components/AddTodo';
export default {
name: 'app',
components: {
Todos,
AddTodo
},
data() {
return {
todos: [
{
id: 1,
title: 'Go workout',
completed: false
},
{
id: 2,
title: 'Do laundry',
completed: false
},
{
id: 3,
title: 'Cook food',
completed: false
},
{
id: 4,
title: 'Clean up room',
completed: false
},
{
i: 5,
title: 'Finish work',
completed: false
}
],
}
},
methods: {
addTodo(newTodoObj) {
this.todos = [...this.todos, newTodoObj];
}
}
}
</script>

The method “addTodo” takes in an todo object and simply adds it to our todos array by using the spread operator. What the spread (…) operator does is copying our current todos array and then adding our new todo object to it.

If you go to localhost:8080 you should now be able to add todos, cool right?!

Marking todos as done

The first thing we are going to do is edit the “Todo” component, we are going to make it so the todo is crossed of if it’s marked as complete. They way we approach this creating a class called “completed” which is going to be conditionally used.

Todo.vue

<template>
<div v-bind:class="{ 'completed': todo.completed }">
<p v-on:click="markComplete">{{ todo.title }}</p>
</div>
</template>
<script>
export default {
name: 'Todo',
props: [
"todo"
],
methods: {
markComplete() {
this.todo.completed = !this.todo.completed
}
}
}
</script>
<style scoped>
.completed {
text-decoration: line-through;
}
</style>

What we’ve done here is creating a class called “completed” in our style tag, then in our template tag we use “v-bind:class” to only use the class if the todo is completed.

We also use a built in event called “click” on the <p> tag, this is going to call a method called “markComplete” whenever you click on the todo item. This method simply changes the todos completed property to the opposite of it’s current value (true or false).

Now you should be able to mark todos as completed by clicking on them.

Deleting todos

We have reached the final step in our todo application, removing todos. This is going to work basically the same way creating todos works. We are going to create an event in the main component that calls a function which takes an ID as argument, then it delete the object in our todos array with the passed ID. The big difference is that as we are emitting the event in the “Todo” component which is a part of the “Todos” component that in turn is a part of the main component, this just means that we need to pass the event two times to reach the top.

This event is going to be emitted by clicking on a button, here is the updated “Todo.vue”:

Todo.vue

<template>
<div v-bind:class="{ 'completed': todo.completed }">
<p v-on:click="markComplete">{{ todo.title }}</p>
<button @click="$emit('delete-todo', todo.id)">Delete</button>
</div>
</template>
<script>
export default {
name: 'Todo',
props: [
"todo"
],
methods: {
markComplete() {
this.todo.completed = !this.todo.completed
}
}
}
</script>
<style scoped>
.completed {
text-decoration: line-through;
}
</style>

We added a button that emits the event “delete-todo” on click and passing the todo id with it.

Here’s how we handle this event in “Todos.vue”:

Todos.vue (template tag)

<template>
<div>
<h2>My todolist</h2>
<ul>
<li v-bind:key="todo.id" v-for="todo in todos">
<Todo v-bind:todo="todo" v-on:delete-todo="$emit('delete- todo', todo.id)"/>
</li>
</ul>
</div>
</template>

Here we have define the event “delete-todo” that emits an event also called “delete-todo”.

Here is “app.vue” where we also have the an event called “delete-todo” that calls the method that removes the todo from our todos array. This is how you pass events from nested components.

app.vue

<template>
<div id="app">
<Todos v-bind:todos="todos" v-on:delete-todo="deleteTodo"/>
<AddTodo v-on:add-todo="addTodo"/>
</div>
</template>
<script>
import Todos from './components/Todos';
import AddTodo from './components/AddTodo';
export default {
name: 'app',
components: {
Todos,
AddTodo
},
data() {
return {
todos: [
{
id: 1,
title: 'Go workout',
completed: false
},
{
id: 2,
title: 'Do laundry',
completed: false
},
{
id: 3,
title: 'Cook food',
completed: false
},
{
id: 4,
title: 'Clean up room',
completed: false
},
{
i: 5,
title: 'Finish work',
completed: false
}
],
}
},
methods: {
addTodo(newTodoObj) {
this.todos = [...this.todos, newTodoObj];
},
deleteTodo(todoId) {
this.todos = this.todos.filter(todo => todo.id !== todoId);
}
}
}
</script>
<style>
</style>

The “deleteTodo” method simply filters out the todo that matches the passed id.

Now you should be able to delete todos and we are ready with the application!

Thank you!

I hope you found this article helpful. I encourage you to really go through the code and try to understand it, if your ambitious, maybe even try to build the application all by yourself!

--

--