Picture of Brian Love wearing black against a dark wall in Portland, OR.

Brian Love

Vue.js for the Angular Dev

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:

This is not a post about:

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:

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:

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:

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:

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

Next, let’s wire up the template using Vue:

<script>
  export default {
    name: 'todo-list',
    props: {
      todos: Array
    }
  }
</script>

Let’s review:

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:

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:

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:

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:

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.

\n```\n\nThe [data option](https://vuejs.org/v2/api/#data) enables reactivity in Vue.js.\nThe 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.\n\nYou can [read more about reactivity in Vue.js](https://vuejs.org/v2/guide/reactivity.html) and how change detection works.\n\n## Create ``\n\nNext, let's create a simple component.\nThis is where we should really start to learn how to build apps using Vue with a component architecture.\n\n🤔 One thing I could not find is a way to use the Vue CLI to generate a new component.\nIt's relatively trivial, but it would be cool if you could generate the scaffolding similar to how the Angular CLI works.\n\nFirst, create a new **src/components/TodoList.vue** file.\nThen, define the `