Click here to Skip to main content
15,885,116 members
Articles / Programming Languages / Typescript

Vue.js with TypeScript for the Angular (2+) Developer

Rate me:
Please Sign up or sign in to vote.
5.00/5 (4 votes)
20 Jul 2019CPOL39 min read 15.1K   258   7   8
Single Page Applications with Vue.js

Download Web API       Download JavaScript version     Download TypeScript version

Introduction

The Big 3 era. If you are a professional basketball fan, you might associate the term “The Big 3” as a trio of basketball players - LeBron James, Dwyane Wade, and Chris Bosh - who played for the Miami Heat of the National Basketball Association (NBA) from 2010 to 2014. The Heat caused a major power shift during the blockbuster 2010 NBA Free Agency, starting the "Big Three" era which started a chaotic period of historical greatness. That team, we can now say with certainty, was the most interesting in NBA history. In other areas of life, the “Big Three” usually references the three most prominent entities in any given grouping or subject. Take for example the current situation in men's professsional tennis. Currently tennis remains under the thumb of the "Big Three" of tennis – Roger Federer, Rafael Nadal and Novak Djokovic. The "Big Three" of tennis have now won all eight Grand Slams since Stan Wawrinka won the 2016 U.S. Open.

In the web development world, we now also have the “Big Three” when it comes to single page applications (SPAs). Single page applications are becoming more and more popular. Facebook, YouTube, Twitter, GitHub, Google and numerous other services are all built using SPA technology. Over the course of the current decade we have seen a growth of interest of three main SPA frameworks: Angular, React and Vue.js with a scattering of interest with other frameworks such as Ember.JS, Aurelia, Meteor.Js etc.

The Evolution of the Single Page Application (SPA)

In the earlier days of web-based application development, JavaScript and jQuery brought significant advances to front-end web development. It provided simple and effective out-of-the-box capabilities such as client-side validation, modal windows, alert messages, animations, and even Ajax-based partial page updates. Enter the world of single page application frameworks and libraries. Single-page applications are web-based applications that load a single HTML page and dynamically updates the page as the user interacts with the application. SPAs use AJAX and HTML5 to create fluid and responsive web application front-ends, without constant page reloads. However, this means much of the work happens on the client side, in JavaScript. The origins of the term single-page application are unclear, though the concept was discussed at least as early as 2003 but often described as a self-contained website using JavaScript in a web application to display the user interface (UI), run application logic, and communicate with a web server.

Overview Of The Big Three Frameworks

Welcome to the JavaScript world as it is now. Frameworks like Angular, React.js or Vue.js became popular because they solve a fundamental issue jQuery left unsolved: Keeping track of the state of our page, the DOM and interacting with it without having to control everything on our own or perform a complete page refresh. Below is a summary of the “Big Three” SPA frameworks and libraries:

  • Angular, developed by Google, was first released in 2010, making it the oldest of the lot. A substantial shift occurred in 2016 with the release of Angular 2 (and the dropping of the “JS” from the original name – AngularJS) as the Angular framework was completely rewritten in TypeScript although AngularJS (version 1) still gets updates. Angular 2+ is simply known as just Angular.
  • React, developed by Facebook, was initially released in 2013. Facebook uses React extensively in their products (Facebook, Instagram, and WhatsApp).
  • Vue, also known as Vue.js, is the youngest member of the group. It was developed by ex-Google employee Evan You in 2014. Over the last few years, Vue.js has seen a substantial shift in popularity, even though it doesn’t have the backing of a large company. For version 3 of Vue.Js, Vue’s contributors and supporters are rewritting the framework using TypeScript.

Vue.js For The Angular Developer 

If you are like me and have been developing single-page applications since it’s early adoption, you probably have written a SPA or two or more using Angular 1 and shifted to Angular 2 using TypeScript when it was released in 2016. Several of my codeproject.com articles feature either Angular 1 or the latest versions of Angular 2 using TypeScript. As software developers and architects, we need to be well versed in several pieces of technology, but as our time is limited, we must pick and choose carefully how we spend our time. Certainly, you don’t want to spend five years learning and using something like Microsoft’s Silverlight technology just to see the web application development community completely abandon it or never embrace it. To this end, I have decided to shift gears and dive into Vue.js. Coming from the Angular SPA world where I have spent much of my time, this article will walk through my experience learning Vue.js, initially using plain-old vanilla JavaScript but ultimately moving towards developing Vue.js using TypeScript.

Learning Vue.js

If you’re a developer coming from an Angular background who is just starting out with Vue.js, jumping in can be both exciting and overwhelming. While everybody’s learning process is very different, I found the following path helped me the most:

  • Visit the Vue.js web site to get an overview of Vue
  • Download and install Vue.js and its accompanying CLI tools
  • Scaffold and create the default Vue.js Hello World application with the Vue.js CLI
  • Learn Vue.js using vanilla JavaScript before venturing into the world of TypeScript
  • Pay for an online course either from plurasight.com or Udemy.com – Maximillian Schwarzmuller's complete Vue.js course on Udemy is excellent and well worth it’s low price tag
  • Learn the major components and aspects of Vue.js including Vue Router and Vuex 
  • Learn a Vue.js UI framework such as Vuetify for Material Design
  • Get familiar with some of the Vue.js development tools
  • Develop a small application using Vue.js

Anatomy of a Vue.js Page

At the heart of every Vue.js single-page application are Vue files. Vue files end with the “.vue” extension and are written with three tags in them: one for the HTML template inside a template tag, one for the JavaScript that controls the page inside a script tag, and finally there’s a style tag to apply CSS to the page. CSS styles can also be scoped to only apply to the current page using the scoped property on the style tag. Vue pages also consist of data properties, methods, computed properties, watchers, life-cycle events and other properties. Computed properties and watchers are two of the most fundamental concepts in Vue.js. For the Angular developer, Vue’s computed properties and watchers are alot like Angular’s observables and provide a way to observe and react to data changes on a Vue instance. By default, a Vue page consists of having all the HTML, JavaScript and CSS for a page contained in a single file. Like Angular, Vue.js uses an HTML-based template syntax that allows you to declaratively bind the rendered DOM to the underlying Vue instance’s data. 

HTML
//
// orders.vue
//
<template>
  <v-layout>
    <!---xs12 sm6 offset-sm3-->
    <v-flex>
      <v-card>
        <v-card-title primary-title>
          <div>
            <h3 class="headline mb-0">Orders</h3>
          </div>
        </v-card-title>

           <v-data-table 
              class="elevation-1" :headers="headers" :hide-actions="true" :loading="loading" 
              :items="orderInquiryViewModel.orders">

          <v-progress-linear v-slot:progress color="blue" indeterminate></v-progress-linear>

          <template v-slot:items="props">
            <td style="min-width: 50px; max-width: 50px; width:50px;">
                 {{ props.item.orderNumber }}
            </td>
            <td style="min-width:50px; max-width: 50px; width:50px;">
                {{ props.item.orderDate | moment("MM/DD/YYYY") }}
            </td>
          </template>

          <template v-slot:no-data>
            <v-alert :value="displayNoDataMessage" color="error" icon="warning">
                     There are no orders
            </v-alert>
          </template>

        </v-data-table>
      </v-card>
    </v-flex>
  </v-layout>
</template>

<style scoped>
a {
  color: white;
}
</style>

<script>
import { OrderInquiryViewModel } from "../viewmodels/order-inquiry.viewmodel";
import { HttpService } from "../services/http-service";

export default {
  components: {
    HttpService
  },
  data() {
    return {
      httpService: null,
      alertMessage: null,
      orderInquiryViewModel: null,
      headers: [
        {
          text: "Order Number",
          align: "left",
          sortable: false,
          value: "OrderNumber"
        },
        { text: "Order Date", value: "OrderDate", sortable: false },
        { text: "Customer Name", value: "CustomerName", sortable: false },
        { text: "Product #", value: "ProductNumber", sortable: false },
        { text: "Description", value: "Description", sortable: false },
        {
          text: "Order Quantity",
          align: "right",
          value: "OrderQuantity",
          sortable: false
        },
        {
          text: "Unit Price",
          align: "right",
          value: "UnitPrice",
          sortable: false
        }
      ],
      loading: true,
      displayNoDataMessage: false
    };
  },
  methods: {
    initializeSearch() {
      this.orderInquiryViewModel.orders = [];
    },
    displayOrders(response) {
      this.orderInquiryViewModel.orders = response.entity;
      this.loading = false;
      if (this.orderInquiryViewModel.orders.length == 0) {
        this.displayNoDataMessage = true;
      }
    },
    displayServerError: function(response) {
      this.loading = false;
      store.dispatch("alert/error", response.returnMessage[0]);
    },
    executeSearch() {
      this.httpService.getOrders().then(function(response) {
        if (response.returnStatus == true) {
          this.displayOrders(response);
        } else {
          this.displayServerError(response);
        }
      });
    }
  },
  created() {
    this.orderInquiryViewModel = new OrderInquiryViewModel();
    this.httpService = new HttpService(this.$http);
    this.displayNoDataMessage = false;
  },
  mounted() {
    this.initializeSearch();
    this.executeSearch();
  }
};
</script>

Building The Sample Application

The sample application for this article is a small shopping cart application and is a follow up to my previous article Test Driving MongoDB with .NET Core 2 which incorporated the latest version of Angular 2 with TypeScript for the front-end. For this article, I will walk through the development of the front-end application, but this time using the latest version of Vue.js while incorporating TypeScript into the mix. The back-end of the sample applicaton for this article will use a modified version of the  .NET Core 2 backend project using a MongoDB database from my previous article mentioned above.

Image 1

The Case for TypeScript

I started learning Vue.js using vanilla JavaScript and initially developed the entire front-end of the sample application for this article using Vue.js with vanilla JavaScript. I feel this is the best way to learn the Vue.js framework as most of the online courses and articles on Vue.js are based on vanilla JavaScript. But as an Angular 2 developer I started to miss TypeScript. TypeScript is an open-source programming language developed and maintained by Microsoft. It is a strict syntactical super set of JavaScript and adds static typing to the language. I like TypeScript but it’s an acquired taste for most developers. Like all things with technology, JavaScript development is deeply rooted by standards and traditions that most JavaScript developers prefer. Afterall, TypeScript code simply just compiles down to the latest versions of JavaScript.

Some of the things I like about developing with TypeScript include the following:

  • Variable Declaration – In TypeScript a variable must be declared before it can be used. This is my favorite feature of TypeScript. Vanilla JavaScript development can incur bugs simply because a variable was misspelled and never caught during testing. The TypeScript compiler will generate errors if we attempt to assign a value to a variable that does not exist or was not found due to it not being declared or was misspelled.
  • Syntax Sugar – TypeScript comes with a syntax that improves the look and feel of JavaScript including classes, interfaces and arrow functions.  It feels like a very natural extension to the JavaScript language and of one of the reasons why many TypeScript features are getting adopted in ECMA specifications.
  • Strongly/Static Typing – Statically typed languages do minimize the number of mistakes you make and improve code analysis so that all your tools like an IDE can provide you hints, assistance and proper refactoring. The TypeScript compiler will also generate errors, if we attempt to assign a value to a variable that is not of the same type. 
  • The future is Now - As mentioned, many of the TypeScript features  have become a part of the ECMA specification. And thanks to compiling (transpiling) you don’t need to wait for browser adoption. Additionally, with both Angular and the next version of Vue.js (version 3) being completely developed internally using TypeScript, it’s safe to say that TypeScript is here to stay for the foreseeable future and perhaps it might even become a JavaScript development standard.

At the end of the day, developing software without bugs is hard. I’m excited about all the new tooling we now have including CLI tools, linters and of course TypeScript that can help us produce less buggy software.

Getting Started - The Vue.js CLI 3.0

Like Angular, Vue.js comes with a full system for rapid Vue.js development. Version 3.0 of the Vue CLI installs the latest version of the Vue.js framework and provides complete project creation and scaffolding. The Vue CLI aims to be the standard tooling baseline for the entire Vue.js ecosystem including installing plugins, integration with webpack for hot-module replacement during development and production deployment features.

After installing the Vue CLI, you can create a new Vue project as follows:

JavaScript
vue create hello-world

You will be prompted to pick a preset. You can either choose the default preset or select "Manually select features" to pick the features you need. For the sample application, I chose to the following features: Babel, TypeScript, Router, Vuex, Linting and Formatting. Additionally, you can also create and manage projects using a graphical interface with the vue ui command that will bring up a CLI UI that makes walking through the project creation and set-up more intuitive.

Image 2

Once created, you can compile, build and start-up the webpack development server and access the application on localhost:8080 using the following CLI command:

JavaScript
npm run serve

Vuetify and Material Design

Before developing your application, it’s important to decide how the user interface will be implemented. Just like choosing any other library or framework, careful research should be taken when selecting a UI framework or library. These UI frameworks and libraries generally come with a proprietary syntax and/or notation. Once your application becomes large, you will be married to that framework – more so than the underlining JavaScript framework you chose. One of the latest trends is Material Design. Material is an adaptable system of guidelines, components, and tools that support the best practices of user interface design. Backed by open-source code, Material streamlines collaboration between designers and developers, and helps teams quickly build beautiful products. For the sample application I selected Vuetify for the application Material Design UI framework. Vuetify complies with the Material Design specification. This means the core features of both Vue.js and Material will be available by default and can be improved by both communities. Vuetify also supports the future of Vue.js tooling through its vue-cli-3 plugin.

Vuetify can also be installed using Vue CLI UI, or from the command line as follows:

JavaScript
Vue add vuetify

Getting Up and Running

As an Angular developer, one thing you will notice is that it’s much easier to get up and running with Vue.js. For the sample application it all starts with the main.ts TypeScript file below. You simply register Vuetify with Vue.js and create an instance of Vue and attach/mount it to an HTML element. You don’t have to configure any modules or any other complicated configurations like Angular requires.

JavaScript
// main.ts

import Vue from "vue";
import AppComponent from "./app.component.vue";
import router from "./router";
import "./plugins/vuetify";
import "./registerServiceWorker";
import Vuetify from "vuetify";
import colors from "vuetify/es5/util/colors";

Vue.use(Vuetify, {
  theme: {
    primary: colors.blue.darken1, // #E53935
    secondary: colors.red.lighten4, // #FFCDD2
    accent: colors.indigo.base // #3F51B5
  }
});

Vue.config.productionTip = false;

new Vue({
  router,
  store,
  render: h => h(AppComponent)
}).$mount("#app");

Main Vue.js TypeScript Page

The main Vue page for the sample application below is written in TypeScript. This page is the master page and serves as the body of the sample application complete with a menu bar at the top and a content section where all other Vue pages will be rendered.  The main Vue page will also contain functionality for displaying user state information and alerts.

JavaScript
// app.component.ts

template src="./app.component.html"></template>

<style>
#app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /*text-align: center;*/
  color: #2c3e50;
  /*margin-top: 60px;*/
}
</style>

<script lang="ts">

import { Component, Vue } from "vue-property-decorator";
import { UserInformationViewModel } from "./viewmodels/user-information.viewmodel";
import AlertComponent from "./alert.component.vue";
import store from "./store";

@Component({
  components: { AlertComponent }
})

export default class AppComponent extends Vue {

  private title: string;

  constructor() {
    super();
    this.title = "VueJs With Typescript";
  }

  public logout() {
    store.dispatch("logoutUser");
  }

  get userInformation() {
    return store.state.userInformation;
  }

  get alert() {
    return store.state.alert;
  }

  get displayProcessing() {
    return store.state.processing;
  }

}

</script>

The main application HTML page has a router-view tag where all the content pages will be injected into the page on route changes. The main application HTML page also contain a progress bar that will display at the top of the content page when HTTP requests are being made.

HTML
<!-- app.component.html -->

<v-app style="height: 100vh;">

<v-toolbar app style="background-color: green; color: white">
    <v-toolbar-title class="headline text-uppercase">
        <span>Code Project</span>
        <span class="font-weight-light">&nbsp;{{title}}</span>
    </v-toolbar-title>

<v-spacer></v-spacer>

<div v-if="userInformation.isAuthenicated==true">
     {{userInformation.firstName}}&nbsp;{{userInformation.lastName}}
</div>
<v-spacer></v-spacer>

<div id="nav">
     <router-link to="/">Home</router-link>&nbsp;|
      <router-link to="/about">About</router-link>&nbsp;|&nbsp;
</div>

<div id="nav" v-if="userInformation.isAuthenicated==true">
     <router-link to="/productsearch">Product Search</router-link>&nbsp;|
     <router-link to="/orders">Orders</router-link>&nbsp;|
     <router-link to="/shoppingcart">Shopping Cart</router-link>
</div>

<div id="nav" style="margin-left:15px;" v-if="userInformation.isAuthenicated==false">
     <router-link to="/register">Register</router-link>&nbsp;|
     <router-link to="/login">Login</router-link>&nbsp;
</div>


<div id="nav" style="margin-left:15px;" v-if="userInformation.isAuthenicated==true">
<router-link v-on:click.native="logout" to="/login">Logout</router-link>&nbsp;
</div>

</v-toolbar>

<v-content>

<v-dialog content-class="progressbar" v-model="displayProcessing" persistent transition="false">
     <v-container fluid fill-height fullscreen transition="false">
         <v-layout justify-center align-center>
             <v-progress-linear height="10" indeterminate color="primary"></v-progress-linear>
         </v-layout>
     </v-container>
</v-dialog>

<router-view />

<v-layout style="position:absolute; top:0; width:100%">

<AlertComponent 
    :messageId="alert.messageId" :messageType="alert.type" :displayMessage="alert.message">

</AlertComponent>

</v-layout>

</v-content>

</v-app>

Decorators and Class-based Components

If you are coming from an Angular (2+) background, you are probably familiar with the pattern of writing components as classes using properties and decorators to describe complex parts of your component. The biggest advantage class-based components have over standard Vue.js components is that they allow for more concise and standard looking code. In the TypeScript code for the sample application I installed and imported both the vue-class-component and the vue-property-decorator npm packages.

A class component is simply a TypeScript class that extends the Vue object. In the AlertComponent below, the Vue object is extended with the @Component decorator and just like that we now have a class-based component in Vue.js. The AlertComponent also used the @Prop decorator to decorate the component’s input properties.

JavaScript
import { Component, Watch, Prop, Vue } from "vue-property-decorator";

@Component
export default class AlertComponent extends Vue {

  public timeoutId: number;
  public originalMessageId: Date;
  public alertModel: any;
  public dismissed: Boolean;

  @Prop() displayMessage: any;
  @Prop() messageType: any;
  @Prop() messageId: any;

  constructor() {}

}

</script>

The vue-property-decorator npm package includes the following seven decorators: @Prop, @PropSync, @Provide, @Model, @Watch, @Inject, @Provide and @Emit. The @Component decorator is provided by the vue-class-component npm package. You can also extend the Vuex store using decorators from the vuex-class npm package. The vuex-class package comes with the following decorators for the Vuex Store: @State, @Getter, @Action, and @Mutation.

The Original Vanilla JavaScript Main Vue.js Page

Below is the original vanilla JavaScript for the main Vue page. This page has three computed properties as implemented with the computed object block structure: one computed property for state information, one for displaying alert messages and one for displaying a progress bar. Rewriting the sample application in TypeScript meant changing the computed properties block with get statements. After looking at the finished product of the sample application, the vanilla JavaScript version of the Vue pages seemed to have a proprietary look and feel to it. Using TypeScript seemed to give the Vue page a cleaner and a more standard and consise natural look and feel; including building classes and components with class constructors. With TypeScript everything looks like a regular class. Suddenly Vue.js started looking a lot like Angular (2+).

HTML
<template>

  <v-app style="height: 100vh;">
    <v-toolbar app style="background-color: green; color: white">
      <v-toolbar-title class="headline text-uppercase">
        <span>Code Project</span>
        <span class="font-weight-light">&nbsp;MATERIAL DESIGN</span>
      </v-toolbar-title>

      <v-spacer></v-spacer>

      <div v-if="userInformation.isAuthenicated==true">
        {{userInformation.firstName}}&nbsp;
        {{userInformation.lastName}}
      </div>

      <v-spacer></v-spacer>

      <div id="nav">
        <router-link to="/">Home</router-link>&nbsp;|
        <router-link to="/about">About</router-link>&nbsp;|&nbsp;
      </div>

      <div id="nav" v-if="userInformation.isAuthenicated==true">
        <router-link to="/productsearch">Product Search</router-link>&nbsp;|
        <router-link to="/orders">Orders</router-link>&nbsp;|
        <router-link to="/shoppingcart">Shopping Cart</router-link>

      </div>

      <div id="nav" style="margin-left:15px;" v-if="userInformation.isAuthenicated==false">
        <router-link to="/register">Register</router-link>&nbsp;|
        <router-link to="/login">Login</router-link>&nbsp;
      </div>


      <div id="nav" style="margin-left:15px;" v-if="userInformation.isAuthenicated==true">
        <router-link v-on:click.native="logout" to="/login">Logout</router-link>&nbsp;
      </div>

    </v-toolbar>

    <v-content>

    <v-dialog content-class="progressbar" 
        v-model="displayProcessing" persistent transition="false">
        <v-container fluid fill-height fullscreen transition="false">
          <v-layout justify-center align-center>
            <v-progress-linear height="10" indeterminate color="primary"></v-progress-linear>
          </v-layout>
        </v-container>
      </v-dialog>

      <router-view/>

      <v-layout style="position:absolute; top:0;  width:100%">
      <AlertComponent :messageId="alert.messageId" :messageType="alert.type" 
                      :displayMessage="alert.message">
     </AlertComponent>
      </v-layout>
    </v-content>

  </v-app>

</template>

<style>

.progressbar {
  opacity: 0.7;
  position: absolute;
  top: 25px;
  height: 25px;
  overflow: hidden;
}

</style>

<script>

import { UserInformationViewModel } from "./viewmodels/user-information.viewmodel";
import AlertComponent from "./components/Alert.component";
import store from "./store";

export default {
  name: "App",
  components: {
    AlertComponent
  },
  data() {
    return {};
  },
  methods: {
    logout() {
      store.dispatch("logoutUser");
    }
  },
  created() {},
  computed: {
    userInformation() {
      return store.state.userInformation;
    },
    alert() {
      return store.state.alert;
    },
    displayProcessing() {
      return store.state.processing;
    }
  }
};
</script>

Separate HTML Template File

Some of the fanfare with Vue.js is that all the HTML templating is combined into the same file with the JavaScript code. This follows the React framework implementation. While developing the vanilla JavaScript version of the sample application, I noticed that I was scrolling up and down in the “.vue” between the HTML template and JavaScript code and losing my place in the file. Of course I could have installed some tooling into Visual Studio Code that would have provided some jumper and anchoring functionality to easily move back and forth in the source file. Perhaps I was just used to developing Angular applications using a separate HTML file and using contact switching tools and commands in Visual Studio Code to quickly switch between the template file and the code file. While developing the TypeScript version of the sample application, I decided to move the HTML template into a separate HTML file for each Vue page. Using the src property of the template tag provided for this capability. As an Angular developer my experience developing Vue pages became familiar to that of Angular.

HTML
<template src="./app.component.html"></template>

Vuex

Large applications can often grow in complexity, due to multiple pieces of state scattered across many components and the interactions between them. To solve this problem, Vue offers Vuex. Vuex is inspired by the React implementation of Redux which helps React developers centralize an application's state and logic which enables powerful state persistence capabilities. Vuex is a state management pattern + library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion. In an Angular application state management is generally managed in a global singleton class object that can be shared across all components through dependency injection. By defining and separating the concepts involved in state management and enforcing rules that maintain independence between views and states, Vuex gives Vue code more structure and maintainability. The Vuex npm package can be installed through the Vue CLI UI or from the command line as follows:

C#
vue install vuex --save

In the main.ts file below, I added an import statement to reference the Vuex package and I simply registered it with Vue.

HTML
// main.ts

import Vue from ‘vue’;
import VueX from ‘vuex’;
Vue.use(Vuex);

The Vuex Store

To manage state in the sample application I created a store.ts file and added code to create a store instance to keep track of the application state using the Vuex.Store function. The Vuex store is managed through mutations and actions. The only way to actually change state in a Vuex store is by committing a mutation. Vuex mutations are very similar to events: each mutation has a string type and a handler. The handler function is where we perform actual state modifications.

JavaScript
//
//  store.ts
//

import Vue from "vue";
import Vuex from "vuex";
import alert from "./alert.module";
import { UserInformationViewModel } from "./viewmodels/user-information.viewmodel";

let userInformationModel: UserInformationViewModel = new UserInformationViewModel();

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    alert
  },
  state: {
    userInformation: userInformationModel,
    processing: false
  },
  mutations: {
    updateUser(state: any, user: UserInformationViewModel): void {
      state.userInformation = user;
    },
    startProcessing(state: any): void {
      state.processing = true;
    },
    stopProcessing(state: any): void {
      setTimeout(function(): void {
        state.processing = false;
      }, 100);
    },
    logoutUser(state: any): void {
      state.userInformation = new UserInformationViewModel();
      localStorage.removeItem("VueToken");
    }
  },
  actions: {
    updateUser(context: any, user: UserInformationViewModel): void {
      context.commit("updateUser", user);
    },
    logoutUser(context: any): void {
      context.commit("logoutUser");
    },
    startProcessing(context: any): void {
      context.commit("startProcessing");
    },
    stopProcessing(context: any): void {
      context.commit("stopProcessing");
    }
  }
});

To invoke a mutation handler, you need to call the store’s commit statement. For example the following line updates the user information in the store.

context.commit("updateUser", user);

Actions are similar to mutations, the differences being that instead of mutating the state, actions commit mutations. Vue pages that wish to update the store can trigger store actions by executing a store.dispatch method. To update the user’s state information from a Vue page the following statement is executed in various Vue pages of the sample application.

store.dispatch("updateUser", userInformation);

User Information View Model

To enhance the development process of a single-page application, I like to use ES6 classes with strongly typed TypeScript properties and using a constructor to initialize the values of each property of the class. The combination of ES6 classes and TypeScript makes the codebase of a single-page application much tighter and less susceptible to coding errors that find their way into production that might have gone uncaught during testing.

The UserInformationViewModel below will be used to store the user’s login information in the Vuex store.

JavaScript
export class UserInformationViewModel {

    constructor() {
        this.id = "";
        this.firstName = "";
        this.lastName = "";
        this.addressLine1 = "";
        this.addressLine2 = "";
        this.city = "";
        this.state = "";
        this.zipCode = "";
        this.emailAddress = "";
        this.phoneNumber = "";
        this.lastLogin = new Date();
        this.isAuthenicated = false;
    }

    public id: string;
    public firstName: string;
    public lastName: string;
    public addressLine1: string;
    public addressLine2: string;
    public city: string;
    public state: string;
    public zipCode: string;
    public emailAddress: string;
    public phoneNumber: string;
    public lastLogin : Date;
    public isAuthenicated: boolean;

}

Managing Store Size and the Codebase

As your application becomes larger in size, the information in the Vuex store will most likely become larger and the code to manage the store could also become larger and unmanageable. To mitigate this problem, you can create separate namespaced modules that can be added to the store codebase. One of the additional features of the sample application is the ability to produce various toaster and alert messages from a centralized place. The Vuex store seemed like the logical place for this functionality. The sample application can raise several different types of messages, informational messages, error messages, warning messages and success messages each using standard colors (red, blue, yellow and orange) that we now see in most applications.

To add to the store and help keep the codebase manageable I created an alerts module as follows:

JavaScript
//
// alert.module.ts
//

import store from "./store";

export const alert: any = {

  namespaced: true,
  state: {
    type: null,
    message: null,
    messageId: null
  },
  mutations: {
    error(state: any, message: any): void {
      state.type = "error";
      state.message = message;
      state.messageId = new Date();
    },
    success(state: any, message: any): void {
      state.type = "success";
      state.message = message;
      state.messageId = new Date();
    },
    warning(state: any, message: any): void {
      state.type = "warning";
      state.message = message;
      state.messageId = new Date();
    },
    info(state: any, message: any): void {
      state.type = "info";
      state.message = message;
      state.messageId = new Date();
    }
  },
  actions: {
    success(state: any, message: any): void {
      store.commit("alert/success", message);
    },
    error(state: any, message: any): void {
      store.commit("alert/error", message);
    },
    warning(state: any, message: any): void {
      store.commit("alert/warning", message);
    },
    info(state: any, message: any): void {
      store.commit("alert/info", message);
    }
  }
};

export default alert;

To add the alert module to the Vuex store, the alert module is simply imported in the store.ts file  and referenced as a module.

JavaScript
//
// store.ts
//

import Vue from "vue";
import Vuex from "vuex";
import alert from "./alert.module";

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    alert
  }
});

Since the alert module is namespaced, raising an alert message from any Vue page is performed by dispatching the action prefixed with the alert namespace along with the type of message you want to display.

JavaScript
store.dispatch("alert/error", response.returnMessage[0]);

Shared Vue.js Components

Like Angular applications, Vue.js also supports reusable shared components that can be used throughout the application. In the sample application, alert messages will be displayed at the top of the page and will have toaster functionality. The below code snippet references an AlertComponent and was added to the main Vue page HTML template.

HTML
<v-layout style="position:absolute; top:0; width:100%">
  <AlertComponent
      :displayMessage="alert.message" :messageId="alert.messageId" :messageType="alert.type">

  </AlertComponent>
</v-layout>

The AlertComponent HTML template consists of the following HTML. Vuetify comes complete with functionality to display toaster messages and message boxes.

HTML
<div class="alert-style"> 
    <v-alert v-if="messageType !== null"
        dismissible  :type="messageType" :value="displayAlertMessage" 
        transition="scale-transition"
        @click="close" :value="displayAlertMessage" >

        {{displayMessage}}

    </v-alert> 
</div>

One of the things you must sometimes overcome when using various UI libraries is that they don’t always provide the exact functionality that you want. The Vuetify alert didn’t have any functionality to have a message box or toaster message automatically dismiss after a number of seconds has elapsed without the user pressing the dismiss button. To provide this functionality I had to extend the message box to close automatically by implementing a timeout functionality that sets a computed property called displayAlertMessage to false after five seconds have elapsed.

JavaScript
<template src="./alert.component.html"></template>

<style scoped>

.alert-style {
  width: 100%;
  position: absolute;
  right: 0;
  z-index: 1000;
}

</style>

<script lang="ts">

import { Component, Watch, Prop, Vue } from "vue-property-decorator";

@Component
export default class AlertComponent extends Vue {
  public timeoutId: number;
  public originalMessageId: Date;
  public alertModel: any;
  public dismissed: Boolean;

  @Prop() displayMessage: any;
  @Prop() messageType: any;
  @Prop() messageId: any;

  constructor() {
    super();
    this.timeoutId = -1;
    this.originalMessageId = new Date();
    this.alertModel = null;
    this.dismissed = false;
  }

  public close() {
    clearTimeout(this.timeoutId);
    this.dismissed = true;
    this.originalMessageId = new Date();
  }

  public created() {
    this.dismissed = false;
  }

  get displayAlertMessage() {
    if (this.messageId != this.originalMessageId && this.dismissed == false) {
      clearTimeout(this.timeoutId);
      this.timeoutId = setTimeout(() => {
        this.originalMessageId = this.messageId;
      }, 5000);

      this.dismissed = false;
      return true;
    } else {
      this.dismissed = false;
      return false;
    }
  }
}
</script>

The AlertComponent has three input properties that includes the type of message to display and the actual message to display. When using TypeScript these properties need to be annotated with the @Prop() annotation as seen above.  The original vanilla JavaScript version of this AlertComponent contained a props block for the input properties. Once again you can see that the TypeScript version of this component has a less proprietary look and feel to it.

JavaScript
<script>
 export default {
    name: "AlertComponent",
    data() {
      return {
          timeoutId: -1,
          originalMessageId: null,
          alertModel: null,
          dismissed: false
        }
    },
    props: {
       displayMessage: String,
       messageType: String,
       messageId: Date
    },
    methods: {
      close() {
        clearTimeout(this.timeoutId);
        this.dismissed = true;
        this.originalMessageId = null;
      }
    },
    created() {
      this.dismissed = false;
    },
    computed: {
       displayAlertMessage: function() {
        if (this.messageId != this.originalMessageId && this.dismissed == false) {  
          clearTimeout(this.timeoutId);
          this.timeoutId = setTimeout(()=>{ 
              this.originalMessageId = this.messageId;
          }, 5000);
          this.dismissed = false;
          return true;
        }
        else {
          this.dismissed = false;
          return false;
        }
      }
    }
 }

</script

Vue.js Router

Vue Router is the official router for Vue.js. It deeply integrates with Vue.js core functionality to make building single page applications with Vue.js a breeze. It’s actually very similar to routing in Angular. You simply associate a route path to a Vue component. In the router.ts file below, the vue-router is imported and registered with the Vue instance. Some additional key points of interest include the following:

  • Meta Fields - In the Vue router you can include a meta field when defining a route. Meta fields are included with the route information when routing occurs in a Vue application. In the code snippet below I created a requiresAuthorization meta field. This field will be used later to protect routes against unauthorized access like the router guard implementation in Angular.
  • Webpack Chunk Names - When building applications with a bundler, the JavaScript bundle can become quite large, and thus affect page load time. It would be more efficient if we could split each route's components into a separate chunk, and only load them when the route is visited. Using webpack chuck names you can group components into chunks. In the code snippet below two chuck files will be created, one for products and one for orders.
  • History Mode - The default mode for the Vue router is hash mode and uses the URL hash to simulate a full URL so that the page won't be reloaded when the URL changes. To get rid of the hash (pound sign) in the URL, we can use the router's history mode, which leverages the history.pushstate API.
JavaScript
//
// router.ts
//

import Vue from "vue";
import Router from "vue-router";
import HomeComponent from "./views/home.component.vue";
import AboutComponent from "./views/about.component.vue";
import RegisterComponent from "./views/register.component.vue";
import LoginComponent from "./views/login.component.vue";

Vue.use(Router);

export default new Router({
  mode: "history",
  base: process.env.BASE_URL,
  routes: [
    {
      path: "/",
      name: "home",
      component: HomeComponent
    },
    {
      path: "/login",
      name: "login",
      component: LoginComponent
    },
    {
      path: "/register",
      name: "register",
      component: RegisterComponent
    },
    {
      path: "/checkout",
      name: "checkout",
      component: () =>
        import(
          /* webpackChunkName: "orders" */ "./views/checkout.component.vue"
        ),
      meta: {
        requiresAuthorization: true
      }
    },
    {
      path: "/orders",
      name: "orders",
      component: () =>
        import(/* webpackChunkName: "orders" */ "./views/orders.component.vue"),
      meta: {
        requiresAuthorization: true
      }
    },
    {
      path: "/productdetail/:id",
      name: "productdetail",
      component: () =>
        import(
          /* webpackChunkName: "products" */ "./views/productdetail.component.vue"
        ),
      meta: {
        requiresAuthorization: true
      }
    },
    {
      path: "/productsearch",
      name: "productsearch",
      component: () =>
        import(
          /* webpackChunkName: "products" */ "./views/productsearch.component.vue"
        ),
      meta: {
        requiresAuthorization: true
      }
    },
    {
      path: "/shoppingcart",
      name: "shoppingcart",
      component: () =>
        import(
          /* webpackChunkName: "orders" */ "./views/shoppingcart.component.vue"
        ),
      meta: {
        requiresAuthorization: true
      }
    },
    {
      path: "/about",
      name: "about",
      component: AboutComponent
    }
  ]

})

Vue.js Router Navigation Guards

As the name suggests, navigation guards provided by the Vue router are primarily used to guard navigations either by redirecting the route request or canceling it. There are several ways to hook into the route navigation process: globally, per-route, or in-component. An alternative to protecting sections of your Vue.js applications is to use global navigation guards with meta fields. With this approach we can register the guards in the Vue router as previously mentioned. In the main.ts file below a router beforeEach hook was added that will execute on every route transition. The hook checks the Boolean meta field requiresAuthorizated as defined in the router definition and when the requiredAuthorization field is set to true the router hook checks the application store to see if the user has been authenticated. If the authentication check fails, the router redirects the application to the login page otherwise the router simply continues on to the requested route.

JavaScript
//
//  router guard implementation - main.ts
//
router.beforeEach(
  (to: any, from: any, next: any): void => {
    let requiresAuthorization: Boolean = to.matched.some(
      (x: { meta: { requiresAuthorization: Boolean } }) =>
        x.meta.requiresAuthorization
    );

    if (requiresAuthorization) {
      if (store.state.userInformation.isAuthenicated === true) {
        next();
      } else {
        next("/login");
      }
    } else {
      next();
    }
  }
);

HTTP RESTful Web API Suppport

When building a modern web application, chances are that you’ll need to consume data from some remote resource, whether it be one that you’ve built or something someone else built. Sending HTTP requests is one of the more popular ways to send data from client facing applications to RESTful Web API backends. Vue.js doesn’t ship with a way to do HTTP out-of-the-box but there are a couple libraries and plugins that you can use such as the popular Axios HTTP client and the vue-resource plugin or the browser's built-in fetch API. I originally implemented the vue-resource plugin for the vanilla JavaScript version of the sample application, but the vue-resource plugin is considered deprecated and many in the Vue world have retired it from official recommendation status. For the TypeScript version of the sample application I implemented the Axios HTTP client. Axios is currently one of the most popular HTTP client libraries and covers almost everything vue-resource provides with a very similar API. In addition, it is universal, supports cancellation, and has TypeScript definitions. Axios is simple and very lightweight, which makes it a great solution for any Vue.js project.

To include axios in your project, execute the following:

npm install axios --save

Implementing a HTTP Singleton Service

The sample application for this article makes RESTful Web API calls to a .NET Core Web API backend. To help encapsulate and centralize the HTTP functionality of the Axios library so that it can be re-used throughout the application without polluting the application with a lot of duplicate HTTP code, I created a HTTP Service class that implements the singleton pattern. A singleton service is a service instance that is shared across components. Implementing the singleton pattern means you are creating only a single instance of the class in the application. The last line in the http.service.ts file creates an instance of the HttpService class and exports it so that any component that needs to use it can simply import it.

JavaScript
//
// http.service.ts
//

import { ResponseModel } from "./viewmodels/response.model";
import axios from "axios";
import store from "./store";

export class HttpService {

  private urlRoot: string;

  constructor() {
    this.urlRoot = "https://localhost:44340/api/secureonlinestore";
  }

  login(requestData: any): any {
    return this.httpPost("/login", requestData);
  }

  register(requestData: any): any {
    return this.httpPost("/register", requestData);
  }

  getProducts(requestData: any): any {
    return this.httpPost("/productinquiry", requestData);
  }

  createOrder(requestData: any): any {
    return this.httpPost("/createOrder", requestData);
  }

  getOrders(): any {
    return this.httpGet("/GetOrders");
  }

  getProductDetail(productId: string): any {
    return this.httpGet("/getproductdetail/" + productId);
  }

  validateEmailAddress(emailAddress: string): any {
    return this.httpGet("/ValidateEmailAddress/" + emailAddress);
  }

  httpGet(urlPath: string): any {
    store.dispatch("startProcessing");
    let url: string = this.urlRoot + urlPath;

    return axios
      .get(url)
      .then(response => {
        store.dispatch("stopProcessing");
        return response.data;
      })
      .catch((error: any) => {
        let response: ResponseModel = new ResponseModel();
        response.returnStatus = false;
        if (error.response) {
          if (error.response.status === "401") {
            response.returnMessage.push("Unauthorized");
          } else {
            response.returnMessage.push(error.response.data.returnMessage[0]);
          }
        } else {
          response.returnMessage.push(error);
        }
        store.dispatch("stopProcessing");
        return response;

      });
  }

  httpPost(urlPath: string, requestData: any): any {
    store.dispatch("startProcessing");
    let url: string = this.urlRoot + urlPath;

    return axios
      .post(url, requestData)
      .then(response => {
        store.dispatch("stopProcessing");
        return response.data;
      })
      .catch((error: any) => {
        let response: ResponseModel = new ResponseModel();
        response.returnStatus = false;
        if (error.response) {
          if (error.response.status === "401") {
            response.returnMessage.push("Unauthorized");
          } else {
            response.returnMessage.push(error.response.data.returnMessage[0]);
          }
        } else {
          response.returnMessage.push(error);
        }
        store.dispatch("stopProcessing");
        return response;
      });
  }
}

export default new HttpService();

Axios HTTP Interceptors 

Axios provides a couple of features inspired by Angular’s $http library.  Axios allows us to add functions called interceptors. These interceptor functions can be attached to either a request or a response when a request is made, or when a response is received. For the sample application the Axios interceptor for Web API requests will serve as a centralized way to send a custom header to the server.  Since the server Web API requires an authorization token for most of its endpoints, the Axios request interceptor will pull from the client’s local storage and add the stored JSON Web Token (JWT) to the header of the request as a “Bearer” token before the request is sent to the server. For the response interceptor, any responses coming from the server that come back with an HTTP status code of 401 – unauthorized will be intercepted and will redirect the user to the login page.

JavaScript
import axios from "axios";

//
//  http interceptors - main.ts
//

axios.interceptors.request.use(
  config => {
    const token: any = localStorage.getItem("VueToken");
    if (token != null && token !== undefined) {
      config.headers.Authorization = "Bearer " + token;
    }
    config.headers.Accept = "application/json";
    return config;
  },
  error => {
    return Promise.reject(error);
  }
);

const UNAUTHORIZED: number = 401;

axios.interceptors.response.use(response => response,
  error => {
    const status: any = error.response;
    if (status === UNAUTHORIZED) {
      router.push({ name: "login" });
    }
    return Promise.reject(error);
  }
);

The Login Component 

To see everything in action, the below login component executes the following functionality:

  • Import the Http Service singleton service
  • Import the Vuex Store
  • Import and initialize the user and user information view models
  • Set up a required validation rule for the Vuetify form
  • When the user presses the login button, the form will be validated
  • The Http Service is executed to post the user’s credentials to the server
  • Upon successful login, the server generated authorization token (JWT) is stored in the user’s local storage
  • The Vuex store is dispatched to update the user’s state information in the Vuex store
  • After successful login the Vue router redirects the user to the product search page
  • If the user’s credentials fail to authenticate, a toaster error message will be displayed
     
JavaScript
//
// login.component.ts
//

<template src="./login.component.html"></template

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { UserViewModel } from "../viewmodels/user.viewmodel";
import { UserInformationViewModel } from "../viewmodels/user-information.viewmodel";
import httpService from "../http.service";
import store from "../store";

@Component
export default class LoginComponent extends Vue {

  public emailAddressErrors: Array<string>;
  public validForm: Boolean;
  public userViewModel: UserViewModel;
  public requiredRules: Array<any>;

  constructor() {
    super();
    this.emailAddressErrors = new Array<string>();
    this.validForm = false;
    this.userViewModel = new UserViewModel();
    this.userViewModel.emailAddress = "";
    this.userViewModel.password = "";
    this.requiredRules = [(v: any) => !!v || "Field is required"];
  }

  public login() {
    let thisComponent: any = this;

    let form: any = this.$refs.form;
    if (form.validate == false) {
      return false;
    }

    httpService.login(this.userViewModel).then(function(response: any) {

      if (response.returnStatus === true) {

        let token = response.entity.token;
        localStorage.setItem("VueToken", token);

        let userInformation = new UserInformationViewModel();

        userInformation.firstName = response.entity.firstName;
        userInformation.lastName = response.entity.lastName;
        userInformation.emailAddress = response.entity.emailAddress;
        userInformation.id = response.entity.id;
        userInformation.phoneNumber = response.entity.phoneNumber;
        userInformation.addressLine1 = response.entity.addressLine1;
        userInformation.addressLine2 = response.entity.addressLine2;
        userInformation.city = response.entity.city;
        userInformation.state = response.entity.state;
        userInformation.zipCode = response.entity.zipCode;
        userInformation.isAuthenicated = true;

        store.dispatch("updateUser", userInformation);

        thisComponent.loginSuccessful();

      } else {
        store.dispatch("alert/error", response.returnMessage[0]);
      }
    });
  }

  public loginSuccessful() {
    this.$router.push({ name: "productsearch" });
  }

}
</script>

Vuetify Form Validation

When it comes to form validation, Vuetify has a multitude of integrations and baked in functionality. You can also choose to use a 3rd party validation plugin such as Vee-validate or Vuelidate. For the sample application I used the baked in validation functionality.

Image 3

 

Below is the HTML template for the registration page which contains a Vuetify form and validations for required fields, email address patterns, password matching confirmations and validations for duplicate email addresses in the back-end database.

HTML
<v-layout>
<v-flex>
    <v-card>
    <v-card-title primary-title>
    <div>
        <h3 class="headline mb-0">Register</h3>
    </div>
    </v-card-title>

    <v-form ref="form" v-model="validForm">
    <v-container fluid>
       <v-layout v-bind="binding">
          <v-flex xs12 lg3 sm6 md6>
          <v-text-field @blur="validateEmailAddress" 
                  v-model="userViewModel.emailAddress" 
                  required :rules="emailRules" :error-messages="emailAddressErrors" 
                  label="Email Address">
          </v-text-field>
          </v-flex>
       </v-layout>

       <v-layout v-bind="binding">
             <v-flex xs12 lg3 sm6 md6>
             <v-text-field v-model="userViewModel.password" required :rules="requiredRules"
                 label="Password"></v-text-field>
             </v-flex>

              <v-flex xs12 lg3 sm6 md6>
              <v-text-field v-model="userViewModel.passwordConfirmation" 
                      required :error-messages="passwordConfirmationErrors" 
                      label="Password Confirmation" :rules="passwordConfirmationRules">
              </v-text-field>
              </v-flex>
       </v-layout>

       <v-layout v-bind="binding">
              <v-flex xs12 lg3 sm6 md6>
              <v-text-field v-model="userViewModel.firstName" 
                      required :rules="requiredRules"
                      label="First Name">
              </v-text-field>
              </v-flex>

              <v-flex xs12 lg3 sm6 md6>
               <v-text-field v-model="userViewModel.lastName" 
                             required :rules="requiredRules"
                             label="Last Name">
               </v-text-field>
              </v-flex>
        </v-layout>

        <v-layout v-bind="binding">
              <v-flex xs12 lg3 sm6 md6>
              <v-text-field v-model="userViewModel.addressLine1" 
                            required :rules="requiredRules"
                            label="Address Line 1"></v-text-field>
              </v-flex>
              <v-flex xs12 lg3 sm6 md6>
              <v-text-field 
                      v-model="userViewModel.addressLine2" 
                      label="Address Line 2">
              </v-text-field>
               </v-flex>
        </v-layout>

        <v-layout v-bind="binding">
               <v-flex xs12 lg3 sm6 md6>
               <v-text-field v-model="userViewModel.city" 
                             required :rules="requiredRules" 
                             label="City">
               </v-text-field>
               </v-flex>

               <v-flex xs12 lg3 sm6 md6>
               <v-text-field v-model="userViewModel.state" 
                             required :rules="requiredRules" 
                             label="State">
               </v-text-field>
               </v-flex>
        </v-layout>

        <v-layout v-bind="binding">
                <v-flex xs12 lg3 sm6 md6>
                <v-text-field v-model="userViewModel.zipCode" 
                              required :rules="requiredRules"
                              label="Zip Code">
                </v-text-field>
                </v-flex>
        </v-layout>

        <v-layout v-bind="binding">
                <v-flex xs12 lg3 sm6 md6>
                <v-text-field v-model="userViewModel.phoneNumber" 
                              required :rules="requiredRules"
                              label="Phone Number">
                </v-text-field>
                </v-flex>
        </v-layout>
    </v-container>

    <v-card-actions>
         <v-btn flat color="primary" :disabled="!validForm" @click="register">Register</v-btn>
    </v-card-actions>

    </v-form>

</v-card>
</v-flex>
</v-layout>

In the email address form field, an @blur event handler is added to the field to validate if the email address is unique and not currently in use in the user database. The email validation rules are assigned to a :rules property and any errors will be displayed by binding to the :error-messages form field property.

HTML
<v-text-field @blur="validateEmailAddress"   
        v-model="userViewModel.emailAddress"   
        required :rules="emailRules" :error-messages="emailAddressErrors" 
        label="Email Address"> 
</v-text-field>

Setting validation rules for Vuetify is as simple as setting up an array of validations. The below array of email validation rules include one for required field and one for making sure the email address follows a valid pattern for an email address.

JavaScript
this.emailRules = [
(v: any) => !!v || "E-mail address is required",
(v: any) => /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/.test(v) || "E-mail address is invalid"
];

On the on blur event will fire the validateEmailAddress method which will reset the email address validation array and will make a web API request to the server which will validate if the email address is unique and doesn't already exist.

JavaScript
public validateEmailAddress(event: any) {

    let vm = this;
    let emailAddress = event.target.value;
    if (emailAddress == null || emailAddress == undefined || emailAddress.length == 0) {
        return false;
    }

    vm.emailAddressErrors = [];

    httpService.validateEmailAddress(emailAddress).then(function(response: any) {
        if (response.returnStatus == false) {
            vm.emailAddressErrors.push(response.returnMessage[0]);
        }
   });

}

One additional type of validation is the password confirmation matching. Since the password validation match needs to match on two fields in the form, the password matching validation needs to be set up as a computed property so it can react to value changes in the form. Using the TypeScript get property sets up the passwordConfirmationRules property as a computered property. The  original vanilla JavaScript version of this page implemented a computed block for this.

JavaScript
get passwordConfirmationRules() {

    let rules = [];

    let rule1 = (v: any) => !!v || "Password is required";
    rules.push(rule1);

    let rule2 = (v: any) => 
            (!!v && v) === this.userViewModel.password ||  `Password confirmation must match`;
    rules.push(rule2);

    return rules;
}

When the user presses the register button, the register method is executed. The first thing you always want to do before submitting the form information to the server is to re-validate that everything on the form is still valid by executing the form.validate method. A user could possibily re-edit a field on the form that might not fire all the required validations on the form. In the register method the reference to the form instance needed to be reassigned to another object with a type of any. This was needed because Vue.js does not have a TypeScript definition for the validate method and referencing it in the code was generating a TypeScript compile error.

JavaScript
public register() {

   let vm: any = this;
   let form: any = this.$refs.form;

   if (form.validate() === false) {
       return false;
   }

   httpService.register(this.userViewModel).then(function(response: any) {
       if (response.returnStatus == true) {
           vm.$router.push({ name: "login" });
       } else {
           store.dispatch("alert/error", response.returnMessage[0]);
       }

   });
}

Vue.js Filters

Below is the Vuetify Material Design template for the Orders page. Two items you will see in the template is that the order dates and the unit prices are being formatted using a filter. Filters in Vue.Js are similar to filters in Angular. Filters are a functionality provided by Vue components that let you apply formatting and transformations to any part of your template data. Filters don’t change component data or anything, but only affect the rendered output. Filters can be scoped two ways: either globally for all components to use or scoped locally for use only in the component that it is defined in.

Image 4

As it turns out Vue.Js doesn’t provide any filters out-of-the-box, but there are a few useful community packages you might find helpful for your projects. For the original vanilla JavaScript version of the Vue.js project for the sample application I downloaded and installed the following two popular npm packages:

  • vue2-filters – this npm package comes complete with over ten types of common filters such as capitalize, uppercase, lowercase, currency, etc.
  • vue-moment – Based on the popular Moment.js filters package to format time values.
HTML
<v-layout>
<!---xs12 sm6 offset-sm3-->
<v-flex>
<v-card>
<v-card-title primary-title>
<div>
<h3 class="headline mb-0">Orders</h3>
</div>
</v-card-title>
<v-data-table :headers="headers" :hide-actions="true" :items="orderInquiryViewModel.orders"
class="elevation-1">
<v-progress-linear v-slot:progress color="blue" indeterminate></v-progress-linear>

<template v-slot:items="props">
<td style="min-width: 50px; max-width: 50px; width:50px;">
    {{ props.item.orderNumber }}
</td>
<td style="min-width:50px; max-width: 50px; width:50px;">
    {{ props.item.orderDate | moment("MM/DD/YYYY") }}
</td>
<td style="min-width:200px; max-width: 200px; width:200px;">
    {{ props.item.customerName }}
</td>
<td style="min-width:150px; max-width: 150px; width:150px;">
    {{ props.item.productNumber }}
</td>
<td style="min-width:200px; max-width: 200px; width:200px;">
    {{ props.item.description }}
</td>
<td style="min-width: 50px; max-width: 50px; width:50px;" class="text-xs-right">
    {{ props.item.orderQuantity }}
</td>
<td style="min-width: 50px; max-width: 50px; width:50px;" class="text-xs-right">
    {{ props.item.unitPrice | currency }}
</td>
</template>

<template v-slot:no-data>
<v-alert :value="displayNoDataMessage" color="error" icon="warning">
    There are no orders
</v-alert>

</template>
</v-data-table>
</v-card>
</v-flex>
</v-layout>

After installing the npm packages for the vue2-filters and vue-moment, all you must do is import the packages and register them with the Vue instance in the main.js start-up code.

JavaScript
//
// maint.ts
//

import Vue from 'vue';
import Vue2Filters from "vue2-filters";
import VueMoment from "vue-moment";

Vue.use(Vue2Filters);
Vue.use(VueMoment);

TypeScript Compatibility and Type Definition Files

So, everything worked out great using the vue filters and moment.js packages and functionality in the vanilla JavaScript Vue.js version of the sample application. Then I started working on the TypeScript version of this project and found out that not everything for Vue.Js has good support for TypeScript. TypeScript is still new to the Vue.js ecosystem. For TypeScript to perform type checking, types need to be defined somewhere. This is where type definition files come into play. They allow you to provide type information for JavaScript code that is by its very nature is not statically typed. The file extension for such a file is “.d.ts, where stands for definition. Type definition files make it possible to enjoy the benefits of type checking, autocompletion, and member documentation. While extremely useful, type definition files take a lot of time to create. Luckily most npm packages and libraries come with type definitions for TypeScript. Unfortunately, neither the vue2-filters nor the vue-moment npm packages come with type definition files. Fortunately, you have options to work around packages that don’t come with TypeScript definition files. One way is to create your own definition files or find one someone else already created that might be available. For the sample application I decided to resolve the TypeScript type definition issue differently. To use the vue-moment package I simply registered the package with a require statement instead of importing it as follows:

JavaScript
Vue.use(require("vue-moment"));

The require statement will dynamically load the vue-moment module thus by-passing the TypeScript type checker.

Custom Global Filters

Vue.js allows you to define filters that can be used to apply common text formatting. As a work-around to the vue2-filters package, I decided to create a custom global filter for the currency formatting functionality that the sample application needed. Looking into the npm package for the vue2-filters, I was able to extract the JavaScript function that provides currency formatting and I added it to the sample application as a custom global filter. The below filters.ts file implements the global custom currency filter.

JavaScript
import Vue from "vue";

//
//  currency filter - filters.ts
//

Vue.filter("currency", (value: any, symbol: string, decimals: number, options: any) => {

    var thousandsSeparator: any,
    var symbolOnLeft: any,
    var spaceBetweenAmountAndSymbol: any;
    var digitsRE: any = /(\d{3})(?=\d)/g;

    options = options || {};

    value = parseFloat(value);

    if (!isFinite(value) || (!value && value !== 0)) {
      return "";
    }

    symbol = symbol != null ? symbol : "$";

    decimals = decimals != null ? decimals : 2;

    thousandsSeparator =
      options.thousandsSeparator != null ? options.thousandsSeparator : ",";

    symbolOnLeft = options.symbolOnLeft != null ? options.symbolOnLeft : true;

    spaceBetweenAmountAndSymbol =
      options.spaceBetweenAmountAndSymbol != null
        ? options.spaceBetweenAmountAndSymbol
        : false;

    var stringified: any = Math.abs(value).toFixed(decimals);

    stringified = options.decimalSeparator
      ? stringified.replace(".", options.decimalSeparator)
      : stringified;

    var _int: any = decimals
      ? stringified.slice(0, -1 - decimals)
      : stringified;

    var i: any = _int.length % 3;

    var head: any =
      i > 0
        ? _int.slice(0, i) + (_int.length > 3 ? thousandsSeparator : "")
        : "";

    var _float: any = decimals ? stringified.slice(-1 - decimals) : "";

    symbol = spaceBetweenAmountAndSymbol
      ? symbolOnLeft
        ? symbol + " "
        : " " + symbol
      : symbol;

    symbol = symbolOnLeft
      ? symbol +
        head +
        _int.slice(i).replace(digitsRE, "$1" + thousandsSeparator) +
        _float
      : head +
        _int.slice(i).replace(digitsRE, "$1" + thousandsSeparator) +
        _float +
        symbol;

    var sign: any = value < 0 ? "-" : "";
    return sign + symbol;

  }

);

Vue.js Mixins 

One of the things I wanted to do in the sample application is to always update the user’s current state whenever the user either started up the application, refreshed the browser window (F5) or whenever they switch to a different route. To do this I needed a global piece of code that could be executed to update the application’s Vuex store. Enter Vue.js Mixins. Mixins are a flexible way to distribute reusable functionality for Vue components. A mixin object can contain any component option. When a component uses a mixin, all options in the mixin will be “mixed” into the component’s own options. When a mixin and the component itself contain overlapping options, they will be “merged” using appropriate strategies. In the below code, a global mixin is being created whenever the created life-cycle event is fired. When the created event is fired, the mixin will check the user’s JSON Web Token (JWT) from local storage and determine if the user’s token has expired. If the token has expired the Vuex store will be updated through a dispatch statement and the user’s state will be set to an unauthenticated state; essentially logging the user out of the application. Since everything is considered a component in Vue.js including shared components and all other visual components, the created event will be fired several times per page.  I only wanted to check the user’s token when a vue component page is being routed to and created; so I put a check in the mixin to check if the event is being fired in the vue page. This is a good example on how to create a global mixin. Of course, the functionality I wanted could have also been done in the foreach event hook on changes in routes.

JavaScript
//
//  mixin for user authenication on page routing
//

Vue.mixin({
  created(): void {

    let vm: any = this;

    if (vm._self.$vnode.data.routerView !== undefined) {
      let user: UserInformationViewModel = new UserInformationViewModel();
          let token: any = localStorage.getItem("VueToken");
          if (token === null || token === undefined) {
            user.emailAddress = "";
            user.firstName = "";
            user.lastName = "";
            user.id = "";
            user.isAuthenicated = false;
            store.dispatch("updateUser", user);

          } else {

            let jwt: any = JSON.parse(atob(token.split(".")[1]));
            let currentTime: number = Date.now() / 1000;
            if (jwt.exp < currentTime) {
              user.emailAddress = "";
              user.firstName = "";
              user.lastName = "";
              user.id = "";
              user.isAuthenicated = false;
              store.dispatch("updateUser", user);
            }
          }
        }
  }
});

When the user refreshes the browser (F5) or reopens up the application in their browser the user’s token can be parsed and the application store can be updated in the main.ts file as follows:

JavaScript
//
//  user authenication on application start-up/refresh - main.ts
//

let user: UserInformationViewModel = new UserInformationViewModel();
user.emailAddress = "";
user.firstName = "";
user.lastName = "";
user.id = "";
user.isAuthenicated = false;

let token: any = localStorage.getItem("VueToken");
if (token == null || token === undefined) {
  user.isAuthenicated = false;
} else {
  let jwt: any = JSON.parse(atob(token.split(".")[1]));

  user.emailAddress = jwt.emailAddress;
  user.firstName = jwt.given_name;
  user.lastName = jwt.nameid;
  user.id = jwt.primarysid;
  user.isAuthenicated = true;

  let currentTime: number = Date.now() / 1000;
  if (jwt.exp < currentTime) {
    user.isAuthenicated = false;
  }
}

store.dispatch("updateUser", user);

Even though JSON Web tokens are base64 encoded, they still can be decoded by anyone. It’s important not to store any critical or private information in these tokens that could be used to compromise the security of your application.

Production Deployment

Single page applications often consist of dozens or even hundreds of components that can be divided into several JavaScript bundle files. When building applications with a bundler, the JavaScript bundle can become quite large, and thus affect page load time. It would be more efficient if we can split several related components into a separate chunk, and only load (lazy-load) them when the route related to a component is visited. Combining Vue's async component loading and webpack’s code splitting feature, it’s easy to lazy-load components based on a route.

In the sample application, routes are configured wth a webpackChunckName that the webpack bundler will use to create separate bundles.

JavaScript
// router.ts

{
    path: "/shoppingcart",
    name: "shoppingcart",
    component: () =>
    import(
        /* webpackChunkName: "orders" */ "./views/shoppingcart.component.vue"
    ),
    meta: { requiresAuthorization: true }
}

To create bundled JavaScript files for production deployment with minification for JS/CSS/HTML, you can simply execute the Vue.js CLI build command that will compile the application and invoke the webpack bundler.

npm run build

The output of a webpack build will go into a dist folder within the project by default. The below list is the result of building the sample application and creating predefined bundles for both products and orders as defined in the Vue.js router configuration.

  File                                      Size             Gzipped

  dist\js\chunk-vendors.132c74f3.js         829.55 KiB       207.68 KiB
  dist\js\orders~products.7efe385e.js       68.99 KiB        17.35 KiB
  dist\js\app.0eed3d02.js                   29.69 KiB        8.35 KiB
  dist\js\products.cf0343bb.js              16.21 KiB        4.37 KiB
  dist\js\orders.77b1fed7.js                14.09 KiB        3.46 KiB
  dist\precache-manifest.8c24b13f01c7e.js    1.16 KiB        0.40 KiB
  dist\service-worker.js                    0.95 KiB         0.54 KiB
  dist\css\chunk-vendors.587a5dd2.css       137.14 KiB       18.01 KiB
  dist\css\orders~products.c472f02d.css     29.67 KiB        4.74 KiB
  dist\css\products.0aa943ff.css            2.75 KiB         0.64 KiB
  dist\css\app.2db963e6.css                 0.45 KiB         0.28 KiB
  dist\css\orders.dd9178be.css              0.36 KiB         0.21 KiB

Vue.js Extension Pack

With its built-in support for development tools like TypeScript and the Chrome Debugger, Visual Studio Code has become the de facto code editor for JavaScript and TypeScript based projects for developers in the Microsoft community. To make things even better there are many extensions for Visual Studio Code that help make Vue.js development a great experience. If you are developing a Vue.js application in Visual Studio Code, you will want to download the Vue VS Code Extension Pack from the Visual Studio Marketplace. This extension pack comes with a collection of extensions for working with Vue applications in Visual Studio Code. The extension pack comes with Vetur and complete support for .vue files. Vetur includes syntax-highlighting, snippets, linting, error checking, formatting, auto completion and debugging. In the extension pack you also get a code formatter called Prettier that will auto-format your code. There are around twenty extension features in the extension pack  that will enhance your Vue.js development experience.

Image 5

Vue DevTools Browser Extension

Using console.log statements in complicated JavaScript code is still a popular and useful way to debug your JavaScript code in the browser. To enhance debugging applications in the browser, Vue has a dedicated plugin that can help you debug and develop your application more efficiently.  Vue DevTools is an extension for both Chrome and Firefox for debugging Vue.js applications. Once installed, the Vue DevTools can be accessed through the Developer Tools (F12) panel in the browser and then going into the Vue tab. 

Some of the features of the Vue DevTools extension for both Chrome and Firefox include:

  • Live edit your component data - One very convenient feature of the Vue DevTools is live editing your component’s data. This lets you quickly test different variations of your component.
  • Debug Vuex with Time Travel - Vue DevTools integrates seamlessly with the Vuex state management library and makes it easy for you to cycle through previous versions of your Vuex state object. This allows you to do what’s known as time-travel debugging. Time travel debugging is the process of stepping back in time through your application data to understand what is happening during execution.
  • Track your app’s custom events - If you’re using events in your application you can track them inside of the events tab of the Vue DevTools.

Image 6

Summary

Developers often describe Vue.js as the birth child of both Angular and React with it’s similarities of both baked into the framework. Both Vue.js and React use similar state management libraries such as Vuex and Redux respectively. React and Vue.js are both unidirectional. Meaning that data flows only in one direction from parent components to child components. Compared to Angular, Vue.js is a more flexible, less opinionated solution. This allows you to structure your application the way you want it to be, instead of being forced to do everything the Angular way. Getting up and running with Vue.js is a much simpler road to travel compared with Angular’s module configuration and declaration requirements. Of course if you are developing a large mission-critical application with a large development team, an opinionated full framework like Angular with big company backing with a large ecosystem might still be the way to go. After rewriting the sample Vue.js application in TypeScript the Vue.js codebase started to look a lot like an Angular 2 application. The lines start to blur a little between Vue.js and Angular once you have a application up and running and even more so when you are using TypeScript. Just like with religion, technology choices are often made based on what you have been raised on. In the end it ultimately comes down to your own preferences. The good news; I don’t see either Vue.js or Angular becoming the next Silverlight; an abandoned technology that was never really embraced.  To complete the trinity of the “Big Three” SPA frameworks, my next codeproject.com article will go deep into React. Stay tuned.

Prerequisites and Running the Sample Application

The back-end for the sample application is a .NET Core Web API application using MongoDB. Between Vue.js, .NET Core and MongoDB, there are a lot of moving parts to install and configure to get the sample application up and running. The sample application consists of two Visual Studio Professional ( 2017 or 2019 ) projects and two Visual Studio Code Projects - one Visual Studio Code project for the vanilla JavaScript Vue.js version and one TypeScript version of the sample application. In an attempt to make getting the sample application up and running in your local development environment as painless as possible, I have outlined below the prerequisites and install steps needed to get up and running.

Software installation prerequisites:

  • MongoDB 4.0 or greater
  • Visual Studio 2017 or 2019 Professional or Community Edition
  • Visual Studio Code
  • .NET Core 2.2 - SDK version 2.2.106
  • NodeJS 10.13.0 or greater
  • Vue.js CLI 3

To get the sample application running, the following steps need to be executed:

  • Install the MongoDB Server as a Service - Download The MongoDB Community Server version 4.0 from their download page. Starting in version 4.0, you can install and configure MongoDB as a Windows Service during the install, and the MongoDB service is started upon successful installation. MongoDB is configured using the configuration file in the installation bin directory.
  • Optionally convert the MongoDB Standalone Server to a Replica Set -  Placing an order in the shopping cart application requires MongoDB transactional support.  If you wish to see this functionality in action; support for MongoDB transactions is needed. To support transactions, convert the MongoDB installation to a replica set; following the instructions mentioned in my previously article, Test Driving MongoDB with .NET Core.
  • Download the Sample Application Source Code - The source code for the sample application can be downloaded from the download source code links at the top of this article. There are three download links, one for the Vue.js front-end using TypeScript and another link for the vanilla JavaScript version of the sample application. The third link will download the MongoDB and .NET Core backend Web API application.
  • .NET Core 2.2 - When you download and install Visual Studio 2017 or 2019 ( Professional or Community Edition ), .NET Core 2.2 should automatically get installed. If you already have Visual Studio, you can verify your installation by going to the Tools menu and selecting Get Tools and Features and this will start the Visual Studio Installer. From the installer options, you can verify that .NET Core 2.2 has been installed. If you need to download the .NET Core SDK for Visual Studio; choose SDK version 2.2.106 for Windows 64 bit operating systems from the .NET Core SDK download page.
  • Vue.js CLI 3 - The Vue.js front-end application is built and served through the Vue.js CLI version 3. You can verify your Vue.js CLI installation by running the Vue.js CLI command:  vue --version.To install the latest version of the Vue CLI execute: npm install -g @vue/cli. The Vue CLI requires Node.js version 8.9 or above (8.11.0+ recommended). Installing the Vue CLI will also install the latest version of Vue.js.
  • Build and Run the Sample Application .NET Core Web API Project - To verify everything has been installed correctly, compile the Web API project CodeProject.Mongo.WebAPI for the sample application. Be sure to wait a minute or two when opening and building these projects with Visual Studio Professional or Community Edition, because Visual Studio will need to restore the packages required to compile these projects when the project is opened.
  • SSL - The Web API project was configured to use SSL. To avoid SSL issues, you'll need to try and run the project by selecting the IISExpress profile and selecting the run button and ASP.NET Core will create an SSL certificate. Depending on your development machine, Visual Studio may ask you if you would like to trust the self-signed certificate that ASP.NET Core has generated. Choose yes to trust the certificate. Because Visual Studio is Visual Studio, you might have to run the project a second or third time or exit and reload Visual Studio to confirm that everything with the project is working. When running the project from Visual Studio, the browser should launch and display output in the browser from the Values controller.
  • Build and Run the Seed Program - To populate the Products collection with test data for the sample application build and run the .NET Core console application CodeProject.Mongo.Import. This program will also create the needed indexes to support the functionality of the sample application.
  • Build and serve the Vue.js front-end application - Both versions of the Vue.js front-end application are dependent on node modules to be installed in the project's node_modules folder. Creating all the node modules can be done by executing  npm install in the DOS command window from the application’s root folder. Once the packages have been installed you can build the Vue.js application project using the Vue,js CLI in a DOS command window  by executing: npm run serve. Once the Vue.js front-end application is built,  webpack will start up a Node.js Express web server in the background.
  • Troubleshooting & fixing Node.js and npm issues - It seems from time-to-time that Node.js and the Node Package Manager (npm) end up having strange issues. These issues you can sometimes be resolved by simply running npm cache clean or running npm install with the -verbose option to see more details. As a last resort, you can uninstall Node.js and delete the %APPDATA% folders npm and npm-cache and reinstall the latest Long-Term Support (LTS) version of Node.js.
  • Launch the sample application - To run the sample application, enter http://localhost:8080 in your browser.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Joey Software Solutions
United States United States
Mark Caplin has specialized in Information Technology solutions for the past 30 years. Specializing in full life-cycle development projects for both enterprise-wide systems and Internet/Intranet based solutions.

For the past fifteen years, Mark has specialized in the Microsoft .NET framework using C# as his tool of choice. For the past four years Mark has been implementing Single Page Applications using the Angular platform.

When not coding, Mark enjoys playing tennis, listening to U2 music, watching Miami Dolphins football and watching movies in Blu-Ray technology.

In between all this, his wife of over 25 years, feeds him well with some great home cooked meals.

You can contact Mark at mark.caplin@gmail.com

...

Comments and Discussions

 
Questiondownload links Pin
baldax5621-Jul-19 23:55
baldax5621-Jul-19 23:55 
download links don't work
AnswerRe: download links Pin
Mark J. Caplin22-Jul-19 0:49
Mark J. Caplin22-Jul-19 0:49 
GeneralRe: download links Pin
baldax5622-Jul-19 0:53
baldax5622-Jul-19 0:53 
Questionformat and images Pin
Nelek21-Jul-19 0:10
protectorNelek21-Jul-19 0:10 
AnswerRe: format and images Pin
Mark J. Caplin21-Jul-19 2:14
Mark J. Caplin21-Jul-19 2:14 
GeneralRe: format and images Pin
Nelek21-Jul-19 11:28
protectorNelek21-Jul-19 11:28 
GeneralRe: format and images Pin
Sean Ewington22-Jul-19 6:19
staffSean Ewington22-Jul-19 6:19 
GeneralRe: format and images Pin
Mark J. Caplin23-Jul-19 14:35
Mark J. Caplin23-Jul-19 14:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.