Introduction
I find the fastest and easiest learning any programming language by doing the meaning projects. By doing a real project, you can benefit from the design, manage the project from start to end. In this article, we will learn to build an application using Vue and state management for Vue application by using Vuex
We will learn the following in this article
- Create a project by using Vue CLI (version 2)
- Create and use the Vue component
- Using Bootstrap Vue to style the component
- Using Vuex for state management
- Authentication with Google firebase authentication
- CRUD events with Google fire store
Source Code
You can find the full source from the GitHub
Prerequiestes
- Install Visual Studio Code (VS Code) from here
- Install NodeJS from here
- Install the Vue CLI from here
npm install -g @vue/cli
Create project
Make sure you have the Vue Command Line Interface (CLI) installed on your machine. You can install Vue CLI by run the command below in your terminal command
Create a todo application project
vue create todo-app
The command above will create a new Vue project with the name todo-app
Change the directory to the new project
cd todo-app
Install Vuex
npm install vuex --save
Install Vue Bootstrap
npm install bootstrap bootstrap-vue --save
Install firebase
npm install firebase --save
Compiles and hot-reloads for development
npm run serve
Compiles and minifies for production
npm run build
Lints and fixes files
npm run lint
Customize configuration
Open the project in VS Code
Open the project in VS Code
code .
You will see the default file structure generated by Vue CLI
We will delete the HelloWorld.vue component for now. We will add our components later.
Open the terminal in the VS Code by pressing the combination key (Ctrl + J or CMD + J)
Create firebase.js
Firebase will provide the authentication and database events (CRUD)
Please make sure you already configured firebase project from Google Firebase
Now create a new named firebase.js
under the src
directory
Copy the following code to the file
import firebase from 'firebase';
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
// Add your firebase config here
};
const firebaseapp = firebase.initializeApp(firebaseConfig);
// database
const db = firebaseapp.firestore();
// authentication
const auth = firebase.auth();
const provider = new firebase.auth.GoogleAuthProvider();
export { auth, provider };
export default db;
Please make sure you modify the configuration firebaseConfig
with yours
Create store object for state management
We want to have all the states located in the store directory under the source (src) directory.
Now we create a new folder named store under the src folder.
Create user state (store)
User state to manage the user who signs in/out by using Google firebase. It will control the user signed in or signed out. And the user interface will be updated based on the user status.
Create a new JavaScript file named user.js
under the store directory.
Copy the following code to the file
const state = {
user: null
};
const getters = {
user: state => state.user
};
const actions = {
signIn ({commit}, user) {
commit('signIn', user);
},
signOut({commit}) {
commit('signOut');
}
};
const mutations = {
signIn: (state, user) => {
state.user = user;
},
signOut: state => state.user = null
};
export default {
state,
getters,
actions,
mutations
};
Create todos state (store)
Todos state to manage the todo list and todo item. Todos state will controll
- Loading data from Google firestore database
- Adding data to firestore database
- Update data to firestore database
- Delete data from firestore database
Create a new JavaScript file named todos.js
under the store directory.
Copy the following code to the file
import db from '../firebase'
// State declaration
const state = {
/**
* each todo item will have the format
* {
* id: String,
* title: String,
* completed: Boolean
* }
*/
todos: []
};
// Getters
const getters = {
// Get all todo items in the current state
allTodos: state => state.todos
};
// actions
const actions = {
/**
* Loading todos
* This is fetch data from database
*/
fetchTodos ({commit}, user) {
db.collection('user')
.doc(user.uid)
.collection('todos')
.onSnapshot(snapshot => {
commit('setTodos', snapshot.docs.map(doc => {
return {
id: doc.id,
doc: doc.data()
}
}));
})
},
/**
* Add a new todo action
* This is fired when the user submit the form add a new todo
* @param {String} payload - The title string of the the new todo
*/
addTodo({ commit}, payload) {
const uid = payload.user.uid;
const title = payload.title;
const newtodo = {
title,
completed: false
};
db.collection('user')
.doc(uid)
.collection('todos')
.add(newtodo)
.then(docRef => {
commit('addTodo', {
id: docRef.id,
data: newtodo
});
})
},
/**
* Update the status of the todo item in the state
* @param {Object} payload - The object contains
* - the id to identify the todo item in the collection list
* - completed property to update the status of the todo item
*/
toggleCompleted ({commit}, payload) {
const uid = payload.user.uid;
const id = payload.id;
const completed = payload.completed;
db.collection('user')
.doc(uid)
.collection('todos')
.doc(id)
.update({
completed: completed
})
commit('toggleCompleted', {
id,
completed
});
},
/**
*
* @param {*} param0
* @param {Number} payload - The number id to identify the todo item in the collection
*/
deleteTodo ({commit}, payload) {
const uid = payload.user.uid;
const id = payload.id;
db.collection('user')
.doc(uid)
.collection('todos')
.doc(id)
.delete();
commit('deleteTodo', id);
}
};
// mutations
const mutations = {
setTodos: (state, todos) => {
state.todos = todos.map(todo => {
return {
id: todo.id,
title: todo.doc.title,
completed: todo.doc.completed
}
});
},
addTodo: (state, {id, data}) => {
console.log('addTodo');
const newtodo = {
id,
title: data.title,
completed: data.completed
};
const oldTodos = [...state.todos];
const index = oldTodos.findIndex(todo => todo.id === id);
// adding new todo the list
if (index < 0) {
state.todos.unshift(newtodo); // = [newtodo, ...state.todos];
}
console.log(state.todos);
},
toggleCompleted: (state, {id, completed}) => {
const index = state.todos.findIndex((todo) => todo.id === id);
if (index >= 0) {
state.todos[index].completed = completed;
}
},
deleteTodo: (state, id) => {
const index = state.todos.findIndex((todo) => todo.id === id);
if (index >= 0) {
state.todos.splice(index, 1);
}
}
};
export default {
state,
getters,
actions,
mutations
};
Create Vuex Store
Now we have states for both user
and todos
, we want to use them in the store collection
Create a new file named store.js
under the src
folder.
Copy the following code to the file
// Create a store
import Vue from 'vue'
import Vuex from 'vuex'
import todos from './todos';
import user from './user';
Vue.use(Vuex);
// create store
export default new Vuex.Store({
modules: {
todos,
user
}
});
Adding store to the project
Open the main.js
to adding store to our application.
We also add the bootstrap to our application here
import Vue from 'vue'
import App from './App.vue'
// Import store
import store from './store/store'
// Bootstrap
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
// import bootstrap and BootstrapVue css
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
Vue.use(BootstrapVue);
Vue.use(IconsPlugin);
Vue.config.productionTip = false;
new Vue({
render: h => h(App),
store
}).$mount('#app')
Create components
Now we have all the set-up. We will start to create our components for the application. Our application will have the following component
-
Login
component - To show user logged in or logged out -
NewTodo
component - This is form component to add a new todo item -
TodoList
component - This is the list view to display each todo item -
TodoItem
component - This is the todo item to be display
Create Login component
We create a new file Login.vue
under the component folder. And copy the following script to the file
<template>
<div class="login">
<div @click="logout" v-if="user" class="logout">
<b-avatar variant="info" :src="imageSrc" class="mr-3"></b-avatar>
<span class="mr-auto">{{user.user.displayName}}</span>
</div>
<b-button v-else variant="primary" @click="login">Sign In</b-button>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { auth, provider } from '../firebase'
export default {
name: 'Login',
data() {
return {
imageSrc: ''
};
},
computed: mapGetters(['user']),
methods: {
...mapActions(['signIn', 'signOut']),
login() {
auth
.signInWithPopup(provider)
.then(authUser => {
this.imageSrc = authUser.user.photoURL;
this.signIn(authUser);
})
.catch(error => alert(error.message))
},
logout() {
this.signOut();
}
}
}
</script>
<style>
.login {
text-align: center;
}
.logout {
cursor: pointer;
}
</style>
Create NewTodo component
We create a new file NewTodo.vue
under the component folder. And copy the following script to the file
<template>
<b-form @submit="AddNewTodo">
<b-form-group
label="Add new todo"
label-for="newTodo"
>
<b-form-input
id="newTodo"
placeholder="What needs to be done"
v-model="todo"
required
>
</b-form-input>
</b-form-group>
<b-button type="submit" variant="primary">Add New Todo</b-button>
</b-form>
</template>
<script>
// import actions
import { mapGetters, mapActions } from 'vuex'
export default {
name: "NewTodo",
data() {
return {
// data for binding to the input text
todo: ''
}
},
computed: mapGetters(['user']),
methods: {
...mapActions(['addTodo']),
/**
* Form submit hanlder
*/
AddNewTodo (event) {
event.preventDefault();
if (this.todo && this.todo !== "") {
// update data base
this.addTodo({
user: this.user.user,
title: this.todo
})
// clear todo
this.todo = "";
}
}
}
}
</script>
<style>
</style>
Create TodoItem component
We create a new file TodoItem.vue
under the component folder. And copy the following script to the file.
TodoItem
will take three properties from the parent (TodoList
)
- id - Identifier the item (this value is generated by firestore database)
- title - This is string represent the item description
- completed - This is a boolean represent the item has been completed or not
<template>
<b-form>
<b-row>
<b-col cols="11">
<b-form-checkbox switch size="lg" :checked="completed"
@change="toggleComplete">
<label :class="{complete: completed}">{{title}}</label>
</b-form-checkbox>
</b-col>
<b-col cols="1">
<b-icon icon="trash" class="hover-trash" @click="removeTodo"></b-icon>
</b-col>
</b-row>
</b-form>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
export default {
name: 'TodoItem',
props: {
id: String, // The todo item key
title: String, // The title description of the todo item
completed: Boolean // indicating if the todo item has been completed
},
computed: mapGetters(['user']),
methods: {
...mapActions(['toggleCompleted', 'deleteTodo']),
toggleComplete () {
// change the status of the todo item
this.toggleCompleted({
user: this.user.user,
id: this.id,
completed: !this.completed
})
},
removeTodo () {
// delete the todo from the state collection
this.deleteTodo({
user: this.user.user,
id: this.id
});
}
}
}
</script>
<style scoped>
.complete {
text-decoration: line-through;
}
.hover-trash:hover {
color: red;
}
.row {
border-bottom: 1px solid gray;
}
</style>
Create NewTodo component
We create a new file TodoList.vue
under the component folder. And copy the following script to the file
<template>
<b-container fluid class="ml-3">
<h1>Todo list goes here</h1>
<ul>
<li v-for="todo in allTodos" :key="todo.id">
<todo-item :title="todo.title" :completed="todo.completed" :id="todo.id" ></todo-item>
</li>
</ul>
</b-container>
</template>
<script>
import TodoItem from './TodoItem.vue'
// vuex actions and getters
import { mapGetters, mapActions } from 'vuex';
export default {
name: 'TodoList',
components: {
TodoItem
},
computed: mapGetters(['allTodos', 'user']),
methods: {
...mapActions(['fetchTodos'])
},
created() {
this.fetchTodos(this.user.user);
}
}
</script>
<style scoped>
ul li {
list-style: none;
}
</style>
Adding components to App component
Open the App.vue
and update the code as follow
<template>
<div class="mt-5">
<img alt="Vue logo" src="./assets/logo.png">
<login></login>
<div v-if="user">
<b-container>
<new-todo></new-todo>
</b-container>
<todo-list></todo-list>
</div>
</div>
</template>
<script>
import NewTodo from './components/NewTodo.vue'
import TodoList from './components/TodoList.vue'
import { mapGetters } from 'vuex'
import Login from './components/Login.vue'
export default {
name: 'App',
computed: mapGetters(['user']),
components: {
NewTodo,
TodoList,
Login
}
}
</script>
<style>
* {
margin: 0;
padding: 0;
}
img {
display: block;
margin-left: auto;
margin-right: auto;
}
</style>
Build and deploy
Now we have completed all the components and have tested the application locally. it is time for build and deploy the application.
Build the application
npm run build
This command will compile our application and generate all neccessary files under the folder dist
. This is the folder will be deployed to google cloud hostting.
Deploy
Please make sure you have firebase-tools
installed globally
npm install -g firebase-tools
Now you will need to login
firebase login
Initalize firebase project
firebase init
Select the hosting option.
When be asked for public directory
, type dist
When be asked for single-page app, type 'y'
Type N
when asking for GitHub automation build.
Type N
when be asked for overwrite the index.html
. We want to keep our file
Then select the hosting project, you can select an existing one if you already created or create a new one.
Deploy the project
firebase deploy
Congrats!!! You have completed build the todo application from start to end, and successfully deploy the application.
Top comments (0)