Learn to build an app using Vue.js from an Angular Dev’s viewpoint.
Angular 💙 React 💚 Vue.js
Let’s be clear.
This is a post about:
- Two frontend JavaScript frameworks.
- Two very popular frameworks.
- Two framworks in the single-page app space.
This is not a post about:
- Choosing one framework over another.
- Pitching one framework as better than the other.
- Starting a flame war 🔥
What this post is really about:
Learning Vue.js from the perspective of an Angular frontend architect and engineer.
An Angular Todo App
Before I created an app using Vue.js I wanted to have a basis for comparison, knowledge transfer and understanding. So, I created a fairly simply “To Do” app using Angular, Angular Material and NgRx. This stack is pretty popular, so I wanted to use a stack that was both popular and that I was familiar with.
Here is a stackblitz of my Angular To Do app:
Download
In this post I’ll be working with a demo Vue.js application. You can clone the repository or download a zip file of the source code:
Installation
We’ll be using the Vue CLI (version 3 of this writing). As such, we need to install the CLI globally using yarn or npm:
$ npm install -g @vue/cli
$ yarn global add@vue/cli
I also recommend that you install the Vue.js devtools. This can be helpful when debugging a Vue app, and it works seamlessly with the Vuex state management library, similar to the Redux Devtools for Redux and NgRx.
Create Project
Next, create a new Vue project using the CLI:
$ vue create todo
You’ll be presented with several options during this process. Here are the options that I chose for this project:
- Please pick a preset: Manually select features
- Check the features needed for your project: Babel, Vuex, Linter
- Pick a linter / formatter config: Prettier
- Pick additional lint features: Lint on save
- Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files
After completing the installation, go ahead and start up the app via:
$ cd todo
$ yarn serve
Remove HelloWorld
To get started, let’s remove the HelloWorld component:
- Remove the src/components/HelloWorld.vue file.
- In src/App.vue, remove the
hello-componentelement in the template. - Then, remove the import statement
- And remove the reference to
HelloWorldin thecomponentsproperty for the app.
Install Vuetify
In doing a little research, it seems the the Veutify component library is very widely used and implements the Material Design. This made the library an easy choice for my app.
$ vue add vuetify
Here are the options that I chose when adding the vuetify project:
- Use a pre-made template? (will replace App.vue and HelloWorld.vue) No
- Use custom theme? No
- Use a-la-carte components? No
- Use babel/polyfill? Yes
With Vuetify installed, open the src/plugins/vuetify.js file so we can specify the theme:
import 'vuetify/dist/vuetify.min.css';
import Vue from 'vue';
import Vuetify from 'vuetify';
import colors from 'vuetify/es5/util/colors';
Vue.use(Vuetify, {
theme: {
primary: colors.indigo,
secondary: colors.pink.accent2,
accent: colors.pink.accent3,
error: colors.red.accent3,
},
});
Update App Template
After installing Vuetify, let’s update the app template in src/App.vue:
<template>
<v-app class="app">
<v-content>
<v-flex xs12 sm8 offset-sm2 md6 offset-md3>
<h1>Hello</h1>
</v-flex>
</v-content>
<v-footer :fixed="fixed" app>
<span>Angular 💚 Vue</span>
</v-footer>
</v-app>
</template>
Most of the markup here is specific to the Vuetify application layout:
- The
v-appcomponent is used to determine breakpoints when using Vuetify. All Vuetify layouts must be wrapped in this component. - We wrap our application’s content in the
v-contentcomponent. - The
v-flexcomponent is part of Vuetify’s flexbox-based layout. For an Angular developer, this is similar to the @angular/flex-layout module. - The
v-footercomponent is out application’s sticky footer.
Go ahead and serve up the app. You should see a simple Application shell with the ‘Hello’ title.
$ yarn serve
Update App
We also need to specify the fixed data value in our app’s root App component to configure Vuetify.
Open src/App.vue and update it as follows:
<script>
export default {
name: 'App',
data() {
return {
fixed: false
}
},
}
</script>
The data option enables reactivity in Vue.js. The data that we specify in a component instance is reactive in that whenever the data is mutated the value is propogated throughout the component, and our application.
You can read more about reactivity in Vue.js and how change detection works.
Create <todo-list>
Next, let’s create a simple component. This is where we should really start to learn how to build apps using Vue with a component architecture.
🤔 One thing I could not find is a way to use the Vue CLI to generate a new component. It’s relatively trivial, but it would be cool if you could generate the scaffolding similar to how the Angular CLI works.
First, create a new src/components/TodoList.vue file.
Then, define the <template> for our new component:
<template>
<v-list>
<todo
v-for="todo in todos"
:key="todo.id"
:todo="todo"
@change="$emit('change', $event)"
></todo>
</v-list>
</template>
Let’s quickly review:
- First, it’s important to note that I’m going to be using the single file approach to building components using Vue. This felt similar to Angular in that we are not defining a component globally using
Vue.component(). It also felt different because the default option for generating a component using the Angular CLI creates separate file for the template, component (script), and styles. - We use the
templateelement to wrap content that will be used to generate the Vue component. I love that Vue uses this standards approach to defining templates (not that Angular does not generally adhere to standards). - Next, we use the
v-listcomponent from Vuetify to create a list. This is similar to themat-listcomponent in the @angular/material module. - Finally, we are going to define a custom
todocomponent that will output the to-do item. When the to-do is changed, we will emit achangeevent to the parent component, passing along the$eventvalue that was emitted.
You’ll also notice that the todo component uses Vue’s builtin directives:
- The
v-foriterates over an iterable, adding and removing elements to/from the DOM as necessary. This is similar to the AngularNgForOfstructural directive. - The
:key="todo.id"directive is similar to Angular’s[key]="todo.id"input binding syntax in templates. It’s also important to note that I’m using the shorthand of thev-binddirective here. The full syntax is:v-bind:key="todo.id". This is some nice syntax sugar provided by Vue, similar to the*ngForshorthand. - We also bind the
todoproperty using thev-bindshorthand syntax::todo="todo". - Note the
@change="doSomething()output binding. This is similar to Angular’s(change)="doSomething($event)syntax. This is also shorthand for Vue’sv-ondirective. The full syntax is:v-on:change="doSomething()". - In order to emit an event to the parent component we use the $emit() instance methjod. First, we specify the event name followed by the value. This is similar to using Angular’s
EventEmitter.emit()method.
Next, let’s wire up the template using Vue:
<script>
export default {
name: 'todo-list',
props: {
todos: Array
}
}
</script>
Let’s review:
- We export a new Vue definition object. This is the same object that is the second argument to the
Vue.component()method if you are not using a single file component. - This object has a
nameproperty that is the selector for the component. This is similar to Angular’sselectorproperty in the configuration object specified with the@Component()decorator. - We then define the properties for the
<todo-list>component in thepropsobject. This is similar to Angular’s input bindings in a component’s class using the@Input()decorator. We declare that the propertytodoswill be anArray.
You can learn more about the Component Basics and Component Registration in the Vue docs.
Create <todo>
With our <todo-list> component created, let’s quickly define the child <todo> component.
Create a new src/components/Todo.vue file and then define the template:
<template>
<v-list-tile>
<v-list-tile-action>
<v-checkbox
v-model="todo.complete"
@change="$emit('change', todo)"
></v-checkbox>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title v-bind:class="{complete: todo.complete}"
>{{ todo.task }}</v-list-tile-title
>
</v-list-tile-content>
</v-list-tile>
</template>
Much of the template is based on using lists with Vuetify. Let’s focus on the Vue-specific syntax:
- The
v-modeldirective binds a form value in Vue. This is similar to Angular’sngModeldirective. We are binding to thecompletevalue of a to-do item. - We use the
@changeevent handler to emit achangeevent to the parent component, specifying the currenttodoobject. Again, this is similar to Angular’s output binding syntax. - We use the
v-bind:classdirective to add the ‘complete’ class to thev-list-tile-titlecomponent if the task has been completed. This is similar to Angular’sngClassdirective. - Finally, we use the double curlybrace syntax for string interpolation in our templates, just like in Angular. Nice 👍.
With the template defined, let’s wire it up:
<script>
export default {
name: 'todo',
props: {
todo: Object
}
}
</script>
<style lang="scss" scoped>
.complete {
text-decoration: line-through;
}
</style>
Similar to the <todo-list> component, we export a configuration object from the module with the name and props properties.
In this case we expect the todo property to be an Object.
We also style up our component a bit using the standard <style> element.
I’m going to use Sass for this project, similar to my Angular project, so we need to specify the lang attribute.
We also scope the styles to this component by specifying the scoped attribute.
This is similar’s to Angular’s view encapsulation.
Update <todo-list>
With the <todo> component created we need to circle back to our <todo-list> component and declare this new child component.
Open src/components/TodoList.vue and add the components property to the vue options object:
<script>>
import Todo from '@/components/Todo.vue';
export default {
name: 'todo-list',
components: {
Todo
},
props: {
todos: Array
}
};
</script>
You might be wondering about the import path. The at-symbol ( @ ) is an alias to /src directory in our project.
Update <app>
Similarly, we also need to update the <app> component to declare the TodoList component.
Open src/App.vue and import the TodoList.vue file, and define the new child component in the components property in the options object:
<script>
import TodoList from '@/components/TodoList.vue';
export default {
name: 'App',
data() {
return {
fixed: false
};
},
components: {
TodoList
}
}
</script>
We also need to specify two more options:
- The
computedproperty - The
methodsproperty
A computed property enables us to define properties for the component that are functions, where we can compute any necessary values.
This is similar to pure pipes in Angular because the value is cached.
From what I could tell the difference is that pure pipes in Angular are cached based on the input value to the transform() method, whereas Vue’s computed properties are cached based on their dependencies.
The methods property defines methods that our component instance can invoke.
In Angular this is somewhat similar to invoking a method in our component.
Open src/App.vue and declare these new properties:
<script>
import TodoList from '@/components/TodoList.vue';
export default {
name: 'App',
data() {
return {
fixed: false
};
},
components: {
TodoList
},
computed: {
todos() {
return [
{
task: 'Watch Ozark Season 2',
complete: false
},
{
task: 'Use Vuex in my awesome app',
complete: true
}
];
},
completeTodos() {
return this.todos.filter(todo => todo.complete);
},
incompleteTodos() {
return this.todos.filter(todo => !todo.complete);
}
},
methods: {
onChange(todo) {
// todo: dispatch action to update state
console.log(todo);
}
}
};
</script>
Let’s quickly review:
- First, we declare a new
computedproperty in the Vue options object. - Each computed property is a getter function that will be invoked by Vue.
- The
todos()computed property returns an array of to-do items. - The
completeTodos()computed property returns an array of to-do items wherecompleteistrue. - The
incompleteTodos()computed property returns an array of to-do items wherecompleteisfalse. - Then, we declare a new
methodsproperty. - The
onChange()function accepts atodo. Eventually we’ll want to dispatch an action to mutate the object in the application’s state.
Finally, update the template to include our new todo-list component.
We’ll use Vuetify to add the list within a Material Design card:
<template>
<v-app class="app">
<v-content>
<v-flex xs12 sm8 offset-sm2 md6 offset-md3>
<h1>Hello</h1>
<v-card class="todo-card">
<v-card-title primary-title>
<h1>To Do List</h1>
</v-card-title>
<todo-list
v-if="incompleteTodos.length > 0"
:todos="incompleteTodos"
@change="onChange"
></todo-list>
<div v-else class="text-xs-center">
<h4>All Done! 🏖</h4>
</div>
</v-card>
</v-flex>
</v-content>
<v-footer :fixed="fixed" app>
<span>Angular 💚 Vue</span>
</v-footer>
</v-app>
</template>
Again, we’ll skip over the details on using Vuetify to create the card.
However, let’s review the newly-added <todo-list> component:
- We use the
v-ifdirective to toggle the display of the list when theincompleteTodoscomputed property has a length greater than zero. - We use the
v-bindshorthand syntax to bind thetodosproperty to theincompleteTodoscomputed property. - We use the
v-onshorthand syntac to invoke theonChange()method when thechangeevent is emitted.
Use Sass Loader
Before our application will build we need to install the Sass loader via npm or yarn:
$ npm install -D sass-loader node-sass
$ yarn add -D sass-loader node-sass
Go ahead and serve the application:
$ npm serve
$ yarn serve
If all goes well, you should see the list of incomplete to-do items:

Create <todo-form>
Obviously, our users need to be able to create new to-do items to add to their list.
So, let’s create a new <todo-form> component.
Create the src/components/TodoForm.vue file:
<template>
<v-form>
<v-text-field
v-model="todo.task"
label="Task"
required
></v-text-field>
</v-form>
</template>
<script>
export default {
name: 'todo-form',
props: {
todo: Object
}
}
</script>
Let’s review the new <todo-form> component:
- Fist, we wrap our form in the
v-formcomponent. This is part of Vuetify. - We also use the
v-text-fieldcomponent to create a Material Design input element. - The
v-modelattribute binds thetaskvalue of thetodoobject to the value of the input. - We export the Vue options object, specifying the
nameandpropsproperties. We are expecting thetodoproperty to be anObject.
Let’s add a new card to the App.vue template, wire up the new child component and add a new onAdd method:
<template>
<v-app class="app">
<v-content>
<v-flex xs12 sm8 offset-sm2 md6 offset-md3>
<h1>Hello</h1>
<v-card class="todo-card">
<v-card-title primary-title>
<h1>To Do List</h1>
</v-card-title>
<todo-list
v-if="incompleteTodos.length > 0"
:todos="incompleteTodos"
@change="onChange"
></todo-list>
<div v-else class="text-xs-center">
<h4>All Done! 🏖</h4>
</div>
</v-card>
<v-card class="todo-card">
<v-card-title primary-title>
<h1>Add To Do</h1>
</v-card-title>
<todo-form :todo="todo"></todo-form>
<v-card-actions class="actions">
<v-btn @click="onAdd" color="primary">Add</v-btn>
</v-card-actions>
</v-card>
</v-flex>
</v-content>
<v-footer :fixed="fixed" app>
<span>Angular 💚 Vue</span>
</v-footer>
</v-app>
</template>
<script>
import TodoForm from '@/components/TodoForm.vue';
import TodoList from '@/components/TodoList.vue';
export default {
name: 'App',
data() {
return {
fixed: false,
todo: {
task: null,
},
};
},
components: {
TodoForm,
TodoList,
},
computed: {
todos() {
return [
{
task: 'Watch Ozark Season 2',
complete: false,
},
{
task: 'Use Vuex in my awesome app',
complete: true,
},
];
},
completeTodos() {
return this.todos.filter((todo) => todo.complete);
},
incompleteTodos() {
return this.todos.filter((todo) => !todo.complete);
},
},
methods: {
onAdd: function () {
// todo: dispatch action to add todo
},
onChange(todo) {
// todo: dispatch action to update state
console.log(todo);
},
},
};
</script>
<style lang="scss" scoped>
.app {
padding: 20px;
.todo-card {
margin: 20px 0;
padding: 20px;
.actions {
flex-direction: row;
justify-content: flex-end;
}
}
}
</style>
I’ve also add some styling to our application. If you serve the app you should see a new form for adding a to-do:
$ npm serve
$ yarn serve

Storing State
In the next post we’ll wire up the Vuex library to store the state of our application, similar to Redux or NgRx.