Brian Love
Google Developer Expert in Angular, software engineer and skier located in Denver, CO

Vue.js for the Angular Dev

Reading time ~12 minutes

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:

  1. Remove the src/components/HelloWorld.vue file.
  2. In src/App.vue, remove the hello-component element in the template.
  3. Then, remove the import statement
  4. And remove the reference to HelloWorld in the components property 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-app component 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-content component.
  • The v-flex component is part of Vuetify’s flexbox-based layout. For an Angular developer, this is similar to the @angular/flex-layout module.
  • The v-footer component 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 template element 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-list component from Vuetify to create a list. This is similar to the mat-list component in the @angular/material module.
  • Finally, we are going to define a custom todo component that will output the to-do item. When the to-do is changed, we will emit a change event to the parent component, passing along the $event value that was emitted.

You’ll also notice that the todo component uses Vue’s builtin directives:

  • The v-for iterates over an iterable, adding and removing elements to/from the DOM as necessary. This is similar to the Angular NgForOf structural 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 the v-bind directive here. The full syntax is: v-bind:key="todo.id". This is some nice syntax sugar provided by Vue, similar to the *ngFor shorthand.
  • We also bind the todo property using the v-bind shorthand 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’s v-on directive. 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 name property that is the selector for the component. This is similar to Angular’s selector property in the configuration object specified with the @Component() decorator.
  • We then define the properties for the <todo-list> component in the props object. This is similar to Angular’s input bindings in a component’s class using the @Input() decorator. We declare that the property todos will be an Array.

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-model directive binds a form value in Vue. This is similar to Angular’s ngModel directive. We are binding to the complete value of a to-do item.
  • We use the @change event handler to emit a change event to the parent component, specifying the current todo object. Again, this is similar to Angular’s output binding syntax.
  • We use the v-bind:class directive to add the ‘complete’ class to the v-list-tile-title component if the task has been completed. This is similar to Angular’s ngClass directive.
  • 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:

  1. The computed property
  2. The methods property

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 computed property 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 where complete is true.
  • The incompleteTodos() computed property returns an array of to-do items where complete is false.
  • Then, we declare a new methods property.
  • The onChange() function accepts a todo. 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-if directive to toggle the display of the list when the incompleteTodos computed property has a length greater than zero.
  • We use the v-bind shorthand syntax to bind the todos property to the incompleteTodos computed property.
  • We use the v-on shorthand syntac to invoke the onChange() method when the change event 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:

Incomplete To Dos

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-form component. This is part of Vuetify.
  • We also use the v-text-field component to create a Material Design input element.
  • The v-model attribute binds the task value of the todo object to the value of the input.
  • We export the Vue options object, specifying the name and props properties. We are expecting the todo property to be an Object.

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

To-do Form

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.

Brian Love

Hi, I'm Brian. I am interested in TypeScript, Angular and Node.js. I'm married to my best friend Bonnie, I live in Denver and I ski (a lot).