When writing, I focus on Angular and full-stack development, with Angular for the frontend.
Recently, I had the opportunity to write a proof-of-concept app using Vue.js to create a basic Todo app using Vue.js and the Vue CLI.
Let's take a look, go over the app, and explore Vue.js. We will also compare some of the concepts and techniques in Vue.js to those in the Angular world.
In brief, whether you are developing Angular or Vue apps, there are some basic concepts and techniques, related to architecturing or structuring your applications, that can be applied to both. For instance, the Smart/Presentational components is universal and applies to both worlds.
Now, the tools differ for sure. Take for example Vuex, which is the Vue.js library for client-side state management. You may compare that to NGRX or any other Angular based state management library.
The major point of this article is that, whatever framework you use to develop your apps, the concepts are yours, and you can take them with you whether you are developing in Vue or Angular.
Here’s a sneak peek at the final product we will build together today:
The source code of this article is available at this GitHub repo.
Let’s start coding the Todo app!
Create Vue app
To start, make sure you install the latest version of the Vue CLI by running this command:
yarn global add @vue/cli
The command downloads and installs the latest bits of the Vue CLI on your machine.
After that, I will create a new Vue.js app using the Vue CLI UI instead of the command line.
Run the following command: vue ui
This command opens a new browser, as shown here:
The home page of the Vue UI allows you to manage your existing projects, create new ones, and import existing projects.
Click on the Create tab, and you will see something similar to this:
Change the system path to the directory where you want to create a new project. In my case, I’ve chosen the path D:\Projects
Locate and click on the Create a new project here button. A new page opens as shown below:
On this page you specify the following:
- Project folder: todo-app
- Project manager: yarn
- Git repository: On
Then, click the Next button to go to the page where you select a preset.
You can choose the Default preset to create the app with default features and options. I’ve chosen the Manual option so that I can customize my app generation.
Locate and click on the Next button to go to the page where you select what features you wish to include in the app.
For this app, I've chosen to use the following features:
- Babel
- Vuex (for state management)
- CSS Preprocessors
- Linter/Formatter
Once done, locate and click on the Next button to go to the page where you have to configure the CSS preprocessors since you’ve added this feature.
In my case, I will pick the Sass/SCSS (with node-sass) for the CSS Preprocessor.
As for linting and formatting, I’ve decided to use ESLint + Prettier to combine both amazing worlds together!
Finally, I want my project to get linted whenever I save a file.
Click the Create Project button to start generating your project files.
NB: Before the project file generation process starts, a prompt will appear asking if you'd like to save your current settings as a preset. This saves all the preferences and configurations for future use if you choose. For now, I will just continue without saving it as a preset.
Once the generation process ends, you get the following project created:
Public folder contains the index.html
file that will be used to run your app.
Src folder contains all of your Vue components and any assets like CSS or images.
The rest are configuration files.
Now that the application is generated, let’s start building the app source code.
Anatomy of a Vue.js app
Let’s explore the main.ts
file and check how a Vue.js app is started:
import Vue from 'vue'
import App from './App.vue'
import store from './store'
new Vue({
store,
render: h => h(App)
}).$mount('#app')
The file creates a new instance of the main Vue
component passing it an object parameter containing configuration settings.
The first argument of the input parameter is the store
object that is imported from ./store
file.
The store
object is injected at this level so that the store object is available to all children components.
The render()
method returns the app.vue
root component to render for this app.
Finally, the $mount(#app)
method is called with a DOM element so that Vue renders the root component in this DOM element inside the index.html
page and activates the app.⁰
<div id="app"></div>
Let’s explore the store we will be using for this app.
Vuex - State Management Simplified
At the core of Vuex is the Store
object to help with state management for your Vue apps:
The store is:
- Reactive by nature. Any change that occurs inside the store is automatically reflected inside the Vue components.
- You cannot directly mutate the store’s state. You do that by committing a mutation to update the store’s state via a pure-function without any side-effects.
To trigger a mutation you dispatch an action.
Let’s look at how the Todo app store looks like. You can check the whole store implementation on this GitHub repo.
The store.js
file starts by registering Vuex
as a plugin to the application so you can access the store’s state later on:
Vue.use(Vuex)
The code then creates a new instance of the Vuex.Store()
object passing an object parameter to configure the state, mutations, actions, and getters of the store.
The state is defined as follows:
state: {
todos: []
},
The state in this app is tracking the list of todo items.
Let's discuss a single mutation in this store:
ADD_TODO(state, todo) {
state.todos = [
{ body: todo, completed: false, id: generateRandom(1, 1000) },
...state.todos
]
}
A mutation accepts, as input parameters, the existing state
object in addition to the todo item to be added to the store’s state.
You can check the rest of mutations in the source code of the store referenced above.
In NGRX - Reactive State for Angular, all the mutations are grouped inside a Reducer. This reducer holds all the mutations and based on the action dispatched, a single mutation is executed.
Let’s continue with the actions
of a store.
addTodo({ commit }, todo) {
commit('ADD_TODO', todo)
}
An action method receives the store object as an input together with the action payload. In the code, the addTodo()
action extracts just the commit
method from the store object. It then calls the commit()
method, passing as parameters, the name of the mutation together with the todo item, to save in the store’s state. So, dispatching an action commits a new mutation to update the store’s state.
Similar to NGRX an action triggers the Reducer to execute a mutation on the store.
Finally, the getters defined on the store object are convenience methods added to allow components to reactively read data from the store’s state. Whenever the store’s state changes, the components listening to the getters will be notified and updated automatically.
Here’s an example getter used in the app:
pendingTodos: state => state.todos.filter(todo => !todo.completed)
pendingTodos
getter, given a state, filters out all completed todo items and returns only those that are still pending.
Vuex Getters are equivalent to Selectors in NGRX.
Now that we know how the Vuex Store works, let’s move on and start building the UI and functionality of the todo app.
Building the Todo app
Let’s start at the App.vue
root component of the app:
The component defines a main header for the home page:
<header>
<div class="navbar navbar-dark bg-dark box-shadow">
<div class="container d-flex justify-content-between">
<TodoBrand />
</div>
</div>
</header>
The app is using [Bootstrap 4] for the layout, and I’ve added the following two lines to add support for Bootstrap in the app.
Locate the public\index.html
and add the following to the <head>
of the document:
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.0/css/all.css"
integrity="sha384-lZN37f5QGtY3VHgisS14W3ExzMWZxybE1SJSEsQp9S+oqd12jhcu+A56Ebc1zFSJ" crossorigin="anonymous">
The head of the App.vue
component defines a custom TodoBrand
Vue.js component to render the brand section of the page.
TodoBrand component
Create a new Vue.js component under the path src\components\TodoBrand.vue
with the following content:
This renders a Todo
SVG icon together with the Todo
header text.
Now you import the TodoBrand
component inside the root App.vue
component:
import TodoBrand from '@/components/TodoBrand.vue'
After that you need to inform App.vue
about this new component by adding it to the list of components managed by App.vue
as follows:
components: {
TodoBrand
}
The body of the App.vue
component has 3 main sections
The header text section showing the number of pending todo items in the store’s state.
<h4>
You have
<strong>{{ pendingTodos.length }}</strong> pending todo(s)
</h4>
The snippet above uses a one-way data binding to bind the pendingTodos
computed variable defined inside the code of the component as follows:
computed: {
pendingTodos() {
return this.$store.getters.pendingTodos
}
}
A computed property in Vue.js is reevaluated every time one of the fields used inside a computed property has changed.
In this example, whenever the this.$store.getters.pendingTodos
results change, this computed variable is reevaluated.
Doesn't this remind you of
RxJS
in Angular? The reactivity nature of both technologies?
TodoAdd component
The App.vue
then renders the TodoAdd
component that, in turn, displays an input field with a button to allow the user to add a new todo item.
<TodoAdd @add-todo="addTodo" />
Create this component inside the src\components
folder, and then import it to the App.vue
component, and finally add it to the list of components managed by this root component.
The TodoAdd
component exposes a single output event the add-todo
event. This event is triggered by the TodoAdd
component whenever the user adds a new todo item.
You would ask yourself why am I handling this event at the App.vue
component rather than inside the TodoAdd
component itself?
I am following the Smart/Presentational concept whereby the Presentational TodoAdd
component handles the DOM events of the input field, and button, and emits a new event to the Smart App.vue
component. The event handler at the App.vue
component level handles this event by communicating directly to the store.
All presentational components have no access to the store object itself. Everything is delegated to the smart components.
Let's explore the TodoAdd
component.
This component uses an input field with a button to allow the user to add a new todo item.
To add a new todo item, you can either enter your todo item text and hit [enter] or click the [+] button. Both ways are handled by the component.
The input field uses a 2-way data-binding option via the v-model
.
data() {
return {
newTodo: ''
}
}
The newTodo
variable is defined under the data()
method on the App.vue
component.
v-model reminds me of the 2-way data binding [(ngModel)] in Angular!
The input field also binds the keyup.enter
event to execute the addTodo()
method whenever the user hits [enter] inside the input field.
The addTodo()
method is defined inside the methods
object property of the App.vue
component as follows:
addTodo() {
if (this.newTodo) {
this.$emit('add-todo', this.newTodo)
this.newTodo = ''
}
}
The method emits the new todo item text, entered by the user, to the outside components.
The $emit
method is defined by Vue, and is globally accessible by all Vue components to emit events to all consumers of this component.
$emit() reminds me of the EventEmitter in Angular app!
Emitting an event involves specifying the name of the event add-todo
, and the payload of the event.
Then back in the App.vue
, you would listen to this event by means of binding to the event name as follows:
<TodoAdd @add-todo="addTodo" />
The addTodo()
method, defined inside App.vue
component, handles this event by dispatching a new action on the store object as follows:
addTodo(todo) {
this.$store.dispatch('addTodo', todo)
}
You dispatch a new action on the store by calling $store.dispatch()
method, and passing it 2 arguments:
- The name of the event to match that define in the
store.js
file. - The payload of the event. In this case, it is the todo item text entered by the user.
The call above is handled by Vuex
and is redirected to execute the action addTodo
defined by the store.js
file.
TodoList component
The final component used inside the App.vue
is the TodoList
component.
<TodoList
:todos="todos"
@change-completed="changeCompleted"
@edit-todo="editTodo"
@delete-todo="deleteTodo"
/>
This component is responsible to render a list of todo items. It accepts as input an array of todo items and exposes 3 output events:
-
change-completed
event -
edit-todo
event -
delete-todo
event
In addition to displaying todo items it also allows the user to:
- Mark a todo item as completed
- Edit the text of an existing todo item
- Delete an existing todo item
App.vue
component imports this component, and adds it to the list of components it manages.
Let's explore this component together:
The component makes use of the TodoItem
component to render every single todo item. We will explore this component in depth shortly.
The component uses the v-for
directive to loop over the todo records, and render a single instance of TodoItem
per todo item.
It is very important to specify the :key="item.id"
for performance enhancement reasons.
v-for
directive is equivalent to*ngFor
directive in Angular.
The component handles the events exposed by TodoItem
component and then re-emits the same events to the parent or outer component, the App.vue
root component in this case.
The events are handled by means of 3 methods defined inside the methods
object property on the TodoList
component.
TodoItem component
Now let’s explore the final component in this app, the TodoItem
component.
For each and every todo item, the component renders the following:
- A checkbox field to mark the todo item as completed (on/off)
- An input field to display the todo item text and allow the user to edit the text
- A button to allow the user to delete the todo item
The text of todo item is coming from the input property todo
defined by the TodoItem
component inside the props
object property.
props
in Vue reminds me of@Input()
in Angular
The component subscribes to the change
event of the checkbox field by executing the changeCompleted()
event handler method, defined inside the methods
object property of the TodoItem
component. The event handler emits the change-completed
event to the outer components to signal a change in the completed
state of a specific todo item.
$emit()
in Vue reminds me of@Output()
in Angular
The component subscribes to the change
event of the input field by executing the editTodo()
event handler method defined inside the methods
object property of the TodoItem
component. The event handler emits the edit-todo
event to the outer components to signal an edit in the text of the todo item. It also provides a payload for this change to be eventually reflected on the store’ stat.
The component subscribes to the click.prevent
event of the delete button by executing the deleteTodo()
event handler method defined inside the methods
object property of the TodoItem
component. The event handler prompts the user for a confirmation of deletion, before emitting the delete-todo
event to the outer components, to signal deletion of the todo item, and eventually reflect that to the store’s state.
That's it! If you visit the App.vue
root component again, you will understand how the component is composed of the TodoBrand
, TodoAdd
, TodoList
, and TodoItem
components.
Finally, to serve the application, run the following command:
yarn serve
Once the app starts, you can access it by navigating your browser to http://localhost:8080
, and playing with the todo app.
Conclusion
I am pretty sure you would agree that the concepts in both Vue and Angular are close. Only the details of how to use this or that feature differ, and you can learn them in no time.
One more feature that I've touched on in this article is the Vue Router module that allows you to define routes in your application. Once again, it resembles that of the Angular Router library.
The Vue.js framework has an extensive documentation website that’s easy to follow, and is always up to date. You can check it here.
This post was written by Bilal Haidar, a mentor with This Dot.
You can follow him on Twitter at @bhaidar.
Need JavaScript consulting, mentoring, or training help? Check out our list of services at This Dot Labs.
Top comments (0)