How to Integrate Typescript with Vue.Js

November 21, 2019Victor Gobel9 min read

Vue and TypeScript logo

A few weeks ago I started a new Vue project. One of my co-dev recommended using TypeScript on our project: "It will help us spot bugs and mistakes, and the sooner we add it, the easier it will be". It was my first time developing on TypeScript.

Why TypeScript ?

TypeScript homepage

First of all, you should now what TypeScript is doing. TypeScript is a language that has the same syntax as Javascript. TypeScript is what we call a superset of Javascript. This means that every Javascript code you write will be a valid TypeScript code, and can be compiled by TypeScript. Therefore, you can add TypeScript to your already existing Javascript project. TypeScript’s main goal is to provide optional static typing and type inference.

// The variable x will be seen as a boolean
let x: boolean

// Here the type inference makes y as a boolean too
let y = true

You can type variables, function parameters, the function returns... TypeScript will then do a static analysis of your code to check dangerous operations (in terms of type). You can accidentally try to assign a variable with the wrong type or access a property of an undefined object. The last issue occurs a lot in run time, you didn't check that your object wasn't null. And there you go, your code crashes...

let x: boolean

/**
If you want to assign another a value with the wrong type 
TypeScript will trigger an error. In my editor I have : 
Assigned expression type "This is a string, not a bool" is not assignable to type boolean
**/
x = 'This is a string, not a bool'


/**
Here 'find' can return undefined if no correct document is found. 
Therefore, accessing the property 'type' will trigger a runtime error
**/
const documents = [{type: 'Review'}, {type: 'Book'}]
const myArticle = documents.find(document => (document.type === 'Article'))

const myArticleType = myArticle.type

With the last example I will have the following error during compilation :

TypeScript strict null check error

Hope this introduction convinced you to use TypeScript. If you are new to it, I suggest reading the handbook

Let's now see how to install it on your Vue project.

Using TypeScript with Vue

Installation of TypeScript

On a new project

If you start a new project you can use the Vue CLI with your own settings and select Typescript in the choices.

Install TypeScript with Vue CLI

Then say yes to use class-style component syntax. We will see later why you should use this syntax.

Use vue class components in Vue CLI

On an existing project

If you add it to an existing project, you can still add TypeScript with NPM :

npm install -g typescript

And you can check the recommanded configuration for your TypeScript config.

Code syntax to use TypeScript in Vue

First, let's tell our Vue compiler that Javascript will be TypeScript. In your Vue file, in the script tag, do the following :

<template>
</template>

<!--Add lang="ts" to specify it's TypeScript-->
<script lang="ts">
</script>

<style>
</style>

Then we need to modify our syntax to be TypeScript friendly.

During the installation (with Vue CLI) I suggested you use class-style component syntax. But other syntaxes exist. In Vue, there is 3 main syntax : the Options API, the Composition API, and the Class API. I suggest you use the latter. But we will see how to use TypeScript with them.

Options API

This syntax is the basic syntax of Vue. But you need to export your component differently. The usual export doesn't have type inference enabled :

<template>
</template>

<script lang="ts">
export default {
  //No type inference
  components: {},
  data() { return {} },
  computed: {},
  created() {},
  methods: {}
}
</script>

<style>
</style>

Therefore, you will need to use Vue.extend syntax to export your Javascript :

<template>
</template>

<script>
import Vue from 'vue';

// Note the Vue.extend
export default Vue.extend({
  //Type inference
  components: {},
  data() { return {} },
  computed: {},
  created() {},
  methods: {}
})
</script>

<style>
</style>

At the beginning of my project, we were using the options API for our Vue files. But we had some issues with TypeScript and decided to use Class API. Honestly, in hindsight, I think some issues came from us not using TypeScript correctly with the options API. Like for example, not typing functions returns. And now with the latest update of TypeScript I cannot reproduce these errors anymore.

But I still suggest you use the class-style component to code as you might, like me, have small issues with the Options API. Moreover, there are more example on the web with class-style components.

Composition API

This syntax is the new syntax of Vue 3. The syntax has been build with TypeScript integration in mind. Therefore, there will be better TypeScript support with the composition API and I think you won't have to change your syntax to use TypeScript. If you want to use this syntax in Vue 2, the composition-api package is available. Vue 3 will be released start of 2020 and is currently in (pre)-alpha. If you have time to take a look at this new syntax I suggest you to start coding with it to familiarise yourself with Vue 3.

But if you prefer to wait for the official release before using the composition API, then take a look at the last syntax available.

Class API

The primary goal of introducing the Class API was to provide an alternative API that comes with better TypeScript inference support.

To use the Class API syntax, you will need to install the package vue-class-component (already installed with Vue CLI).

Then create a Vue component using the following syntax :

import Vue from 'vue'
import Component from 'vue-class-component'

@Component({
  props: {
    myProp: {
      type: String
    }
  }
})
export default class HelloWorld extends Vue {
  myData: {name: string} = {name: 'test'}

  // computed are declared with get before a function
  get myComputed(): string {
    return this.myData.name
  }

  created() {}

  // methods are just functions
  myMethod(): boolean {
    return false
  }
}

You will get used to this syntax in no time!

One advantage of this syntax is that you can regroup 'methods' and 'computed' by functionality (the composition API is also built to do that).

import Vue from 'vue'
import Component from 'vue-class-component'

@Component({})
export default class HelloWorld extends Vue {
  // Autocomplete functionality
  get computedRelatedToAutocomplete() {
    ...
  }
  methodRelatedToAutocomplete() {
    ...
  }

  // Search Functionality
  get computedRelatedToSearch() {
  ...
  }
  methodRelatedToSearch() {
  ...
  }
}

The package vue-class-component is fully supported by Vue 2.

One more improvement we can make is by using the package vue-property-decorator. This package is also installed by Vue CLI.

With it, even the props will be inside the definition of the component :

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

@Component
export default class HelloWorld extends Vue {
  // The props are defined here with the annotation @Prop()
  @Prop()
  private msg!: string;

  myData: {name: string} = {name: 'test'}

  // computed are declared with get before a function
  get myComputed(): string {
    return this.myData.name
  }

  // methods are just functions
  myMethod(): boolean {
    return false
  }
}

Drawback : The Class API is still not perfect with TypeScript, and it will be replaced by the Composition API in Vue 3. After Vue 3 is released I strongly encourage you to use the Composition API.

But currently, it's our best solution! And changing from one notation to another is not that complicated. You can change one file at a time and still run the other files with another syntax. That is what we did to change from the options API to the class API. And even today, we still have old files with the options API syntax (that we are migrating little by little).

Actually, you can also implement TypeScript one file at the time with Vue (if you add it to an existing project). You take one of your Vue files and add the lang="ts" inside the script tag. Then you modify your component to use the Class API and fix the potential errors TypeScript found. It's really easy!

Tips and tricks

Type your props in Vue

In your project, you will want to type your props. Indeed, the type you define for your props should be one from Vue type. You can use Boolean, String, Array but no self-made type.

import Vue from 'vue';

type MySuperType = {
  name: string
  age: number
}

export default Vue.extend({
  props: {
    mySuperProps: {
      type: MySuperType, // Will not work
      required: true
    }
  }
})

You will need to use another syntax :

import Vue from 'vue';

type MySuperType = {
  name: string
  age: number
}

export default Vue.extend({
  props: {
    mySuperProps: {
      type: Object as () => MySuperType,
      required: true
    }
  }
})

Using Typescript with Vuex

If you plan to use Vuex in your project, then you can also Type your stores. In this article talking about Vuex and TypeScript you will find a detailed explanation on how to type your store and how to use the actions/state/getters inside a component with the Class API.

Access property on a nullable object

Sometimes you will have a nullable object. Before accessing the property of the object you will need to check if the object is not null. But you cannot do this check anywhere. It needs to be close to the access of the property.

Here are some examples/tricks for nullable objects :

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

@Component
export default class HelloWorld extends Vue {
  value: null | {test: string} = null
  setValue() {
    this.value = {test: 'hello'}
  }

  /**
   * this.value can be null,
   * we cannot access the property test on null
   *
   * An error will be triggered
   */
  methodWithTypeError() {
    this.value.test //error
  }

  /**
   * checking that value is not null before accessing property
   * will tell TypeScript that the variable is safe
   *
   * No error will be triggered
   */
  methodWithNoError() {
    if(this.value === null) {
      return
    }
    this.value.test
  }

  /**
   * When performing its analysis TypeScript will not check the whole code
   * Therefore, even if we have checked this.value before
   * it will still see it as null inside the map function
   *
   * An error will be triggered
   */
  methodWithErrorWhereItShouldBeOK() {
    {
      if(this.value === null) {
        return
      }
      const array = [1, 2, 3]
      array.map((arrayValue: number) => {
        return arrayValue + this.value.test //error
      })
    }
  }

  /**
   * Here by assigning this.value.test to
   * another variable just after the null test,
   * TypeScript will infer its type as not null
   *
   * No error will be triggered
   */
  fix() {
    {
      if(this.value === null) {
        return
      }
      let test = this.value.test
      const array = [1, 2, 3]
      array.map((arrayValue: number) => {
        return arrayValue + test
      })
    }
  }
}

TypeScript error when accessing a property of a nullable object

Another solution for this kind of problem is to use the function get of lodash and have a default value returned if the object is null. But with this solution, you don't use TypeScript when accessing a property...

 

It took me some time to get used to TypeScript. In the beginning, I didn't understand the full strength of TypeScript and thought it was extra work. But as time went by, I started to get used to TypeScript and my Javascript code was more structured. It is now easier to develop, especially when coding along with other developers : TypeScript allows you to give a shape to your object, thus making them more understandable to others.

Hope this post convinced you to use TypeScript and that it was useful to you.

Thanks for your time!

Victor Gobel

Victor Gobel

Web Developer at Theodo