Vue.js is a progressive JavaScript framework used for building user interfaces. One of its core strengths is its flexibility and ease of use, which allows developers to quickly build and manage dynamic applications. In this practical tutorial, we will create a simple yet functional Todo app using Vue.js. This application will allow users to add, display, update, and delete todos, providing a hands-on experience with Vue.js components, state management, and event handling.
A Todo app is a classic beginner project that demonstrates fundamental web development concepts. By building this application, you will gain a solid understanding of how to structure Vue.js projects, manage component state, and implement CRUD (Create, Read, Update, Delete) operations. This comprehensive guide will take you step-by-step through the process, ensuring you have all the necessary knowledge to create your own Vue.js applications.
Setting Up the Development Environment
Installing Vue CLI
Before we start building our Todo app, we need to set up our development environment. Vue CLI (Command Line Interface) is a powerful tool that helps you create and manage Vue projects. To install Vue CLI, you must have Node.js and npm installed on your computer. Once you have these prerequisites, open your terminal and run the following command:
npm install -g @vue/cli
This command installs Vue CLI globally on your system, allowing you to use the vue
command to create and manage Vue projects.
Creating a New Vue Project
With Vue CLI installed, you can now create a new Vue project. Run the following command in your terminal:
vue create todo-app
You will be prompted to select a preset for your project. Choose the default preset, which includes Babel and ESLint. Once the setup is complete, navigate into the project directory:
cd todo-app
To start the development server, run:
npm run serve
This command will compile your application and start a local development server. You can view your application in the browser at http://localhost:8080
.
Creating the Todo App Structure
Defining the App Component
The App.vue
file is the root component of our Vue application. Let’s start by defining the basic structure of our Todo app in App.vue
:
<template>
<div id="app">
<h1>Todo App</h1>
<AddTodo @add-todo="addTodo" />
<ul>
<TodoItem
v-for="(todo, index) in todos"
:key="index"
:todo="todo"
@delete-todo="deleteTodo(index)"
/>
</ul>
</div>
</template>
<script>
import AddTodo from './components/AddTodo.vue';
import TodoItem from './components/TodoItem.vue';
export default {
name: 'App',
components: {
AddTodo,
TodoItem
},
data() {
return {
todos: []
};
},
methods: {
addTodo(newTodo) {
this.todos.push(newTodo);
},
deleteTodo(index) {
this.todos.splice(index, 1);
}
}
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
margin-top: 60px;
}
</style>
In this code, we define a root component that includes a heading, the AddTodo
component for adding new todos, and a list of TodoItem
components for displaying each todo. We also define two methods: addTodo
to add a new todo to the list and deleteTodo
to remove a todo from the list.
Creating the TodoItem Component
Next, let’s create the TodoItem
component. Create a new file named TodoItem.vue
in the src/components
directory and add the following code:
<template>
<li>
<span>{{ todo.text }}</span>
<button @click="$emit('delete-todo')">Delete</button>
</li>
</template>
<script>
export default {
name: 'TodoItem',
props: {
todo: {
type: Object,
required: true
}
}
};
</script>
<style scoped>
li {
list-style: none;
margin: 10px 0;
}
button {
margin-left: 10px;
}
</style>
In this code, the TodoItem
component receives a todo
prop and emits a delete-todo
event when the delete button is clicked. This event is captured by the parent component (App.vue
), which removes the todo from the list.
Creating the AddTodo Component
Now, let’s create the AddTodo
component. Create a new file named AddTodo.vue
in the src/components
directory and add the following code:
<template>
<div>
<input v-model="newTodo" @keyup.enter="submitTodo" placeholder="Add a new todo" />
<button @click="submitTodo">Add</button>
</div>
</template>
<script>
export default {
name: 'AddTodo',
data() {
return {
newTodo: ''
};
},
methods: {
submitTodo() {
if (this.newTodo.trim() !== '') {
this.$emit('add-todo', { text: this.newTodo });
this.newTodo = '';
}
}
}
};
</script>
<style scoped>
input {
padding: 8px;
margin-right: 5px;
}
button {
padding: 8px;
}
</style>
In this code, the AddTodo
component includes an input field and a button. When the user types a new todo and presses enter or clicks the button, the submitTodo
method is called. This method emits an add-todo
event with the new todo as payload and clears the input field.
Managing State in Vue
Using Data Properties
In Vue.js, component state is managed using data properties. In our App.vue
component, we define a todos
data property to hold the list of todos. This state is reactive, meaning any changes to it will automatically update the DOM.
Handling Methods and Events
Methods in Vue.js are defined in the methods
option. These methods can be bound to DOM events using the @
directive. In our App.vue
component, the addTodo
and deleteTodo
methods are responsible for updating the state.
When a new todo is added, the AddTodo
component emits an add-todo
event, which is captured by the parent component. The addTodo
method is then called, adding the new todo to the todos
array. Similarly, when the delete button in a TodoItem
component is clicked, it emits a delete-todo
event, which triggers the deleteTodo
method in the parent component.
Implementing CRUD Operations
Adding a New Todo
To add a new todo, the AddTodo
component captures user input and emits an add-todo
event. The parent component handles this event and updates the todos
state. This process is illustrated in the following code:
<template>
<div id="app">
<h1>Todo App</h1>
<AddTodo @add-todo="addTodo" />
<ul>
<TodoItem
v-for="(todo, index) in todos"
:key="index"
:todo="todo"
@delete-todo="deleteTodo(index)"
/>
</ul>
</div>
</template>
<script>
import AddTodo from './components/AddTodo.vue';
import TodoItem from './components/TodoItem.vue';
export default {
name: 'App',
components: {
AddTodo,
TodoItem
},
data() {
return {
todos: []
};
},
methods: {
addTodo(newTodo) {
this.todos.push(newTodo);
},
deleteTodo(index) {
this.todos.splice(index, 1);
}
}
};
</script>
Displaying Todos
Todos are displayed using the v-for
directive to iterate over the todos
array. Each todo is passed as a prop to the TodoItem
component, which renders the todo text and provides a delete button.
<ul>
<TodoItem
v-for="(todo, index) in todos"
:key="index"
:todo="todo"
@delete-todo="deleteTodo(index)"
/>
</ul>
Updating a Todo
Updating a todo can be implemented by adding an edit button and an input field in the TodoItem
component. Here is an example:
<template>
<li>
<span v-if="!isEditing">{{ todo.text }}</span>
<input v-else v-model="newText" @keyup.enter="saveTodo" />
<button @click="isEditing = true" v-if="!isEditing">Edit</button>
<button @click="saveTodo" v-else>Save</button>
<button @click="$emit('delete-todo')">Delete</button>
</li>
</template>
<script>
export default {
name: 'TodoItem',
props: {
todo: {
type: Object,
required: true
}
},
data() {
return {
isEditing: false,
newText: this.todo.text
};
},
methods: {
saveTodo() {
this.$emit('update-todo', this.newText);
this.isEditing = false;
}
}
};
</script>
In this code, the TodoItem
component includes an edit button that toggles the isEditing
state. When editing, an input field is displayed, allowing the user to update the todo text. The saveTodo
method emits an update-todo
event with the updated text.
To handle this event in the parent component, update App.vue
:
<template>
<div id="app">
<h1>Todo App</h1>
<AddTodo @add-todo="addTodo" />
<ul>
<TodoItem
v-for="(todo, index) in todos"
:key="index"
:todo="todo"
@delete-todo="deleteTodo(index)"
@update-todo="updateTodo(index, $event)"
/>
</ul>
</div>
</template>
<script>
import AddTodo from './components/AddTodo.vue';
import TodoItem from './components/TodoItem.vue';
export default {
name: 'App',
components: {
AddTodo,
TodoItem
},
data() {
return {
todos: []
};
},
methods: {
addTodo(newTodo) {
this.todos.push(newTodo);
},
deleteTodo(index) {
this.todos.splice(index, 1);
},
updateTodo(index, newText) {
this.todos[index].text = newText;
}
}
};
</script>
Deleting a Todo
Deleting a todo is handled by emitting a delete-todo
event from the TodoItem
component and removing the todo from the todos
array in the parent component:
<template>
<li>
<span>{{ todo.text }}</span>
<button @click="$emit('delete-todo')">Delete</button>
</li>
</template>
<template>
<div id="app">
<h1>Todo App</h1>
<AddTodo @add-todo="addTodo" />
<ul>
<TodoItem
v-for="(todo, index) in todos"
:key="index"
:todo="todo"
@delete-todo="deleteTodo(index)"
/>
</ul>
</div>
</template>
<script>
import AddTodo from './components/AddTodo.vue';
import TodoItem from './components/TodoItem.vue';
export default {
name: 'App',
components: {
AddTodo,
TodoItem
},
data() {
return {
todos: []
};
},
methods: {
addTodo(newTodo) {
this.todos.push(newTodo);
},
deleteTodo(index) {
this.todos.splice(index, 1);
}
}
};
</script>
Styling the Todo App
Styling is an essential part of creating an appealing and user-friendly application. In Vue.js, you can apply scoped styles to individual components to prevent style leakage. Here is an example of styling our Todo app:
App.vue:
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
margin-top: 60px;
}
h1 {
color: #42b983;
}
ul {
padding: 0;
max-width: 400px;
margin: 0 auto;
}
li {
background: #f9f9f9;
margin: 5px 0;
padding: 10px;
border-radius: 4px;
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
AddTodo.vue:
<style scoped>
input {
padding: 8px;
margin-right: 5px;
width: 70%;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
padding: 8px;
border: none;
background: #42b983;
color: white;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #369971;
}
</style>
TodoItem.vue:
<style scoped>
button {
padding: 4px 8px;
border: none;
background: #ff4d4d;
color: white;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #d93636;
}
</style>
These styles provide a clean and modern look to our Todo app, enhancing the user experience.
Conclusion
In this practical tutorial, we built a simple yet functional Todo app using Vue.js. We covered the basics of setting up a Vue project with Vue CLI, creating and managing Vue components, and implementing CRUD operations. We also discussed state management, event handling, and styling components.
By following this guide, you have gained a solid understanding of how to create and manage Vue.js applications. The concepts and techniques learned here can be applied to more complex projects, allowing you to build powerful and dynamic web applications with Vue.js.
Additional Resources
To further expand your knowledge of Vue.js and enhance your development skills, here are some additional resources:
- Vue.js Documentation: The official Vue.js documentation is a comprehensive resource for understanding the framework’s capabilities and usage. Vue.js Documentation
- Vue Mastery: An excellent platform offering tutorials and courses on Vue.js. Vue Mastery
- Vue School: Another great resource for learning Vue.js through video courses. Vue School
- Books: Books such as “The Majesty of Vue.js” by Alex Kyriakidis and Kostas Maniatis provide in-depth insights and practical examples.
- Community and Forums: Join online communities and forums like Vue Forum, Reddit, and Stack Overflow to connect with other Vue developers, ask questions, and share knowledge.
By leveraging these resources and continuously practicing, you’ll become proficient in Vue.js and be well on your way to developing impressive and functional web applications.