Building Vue.js Applications With TypeScript - Part Two
Tags: typescript, vue, frontend, javascript
Published: 2019-04-23 13:09:29

*This article was originally posted on my blog here - [https://www.georgehanson.co.uk/blog/building-vuejs-applications-with-typescript-part-two](https://www.georgehanson.co.uk/blog/building-vuejs-applications-with-typescript-part-two)*

A while ago, I posted an article titled "Building VueJS Applications with TypeScript". If you haven't seen that yet, make sure you take a look. You can find it by clicking [here.](https://dev.to/georgehanson/building-vuejs-applications-with-typescript-1j2n)

At the end of that post, I noted that I would be writing a follow-up article explaining methods, props, child components, etc. While it has been a while since I posted that, this is the second post in the series.

In this article, we will carry on where we left off. But we are going to create a very simple to-do list application to demonstrate a variety of concepts. Let's get started.

## Setting Up

The first thing that we need to do is to create a new component for our to-do list. Within your `src/components` folder, create a new file called `ToDo.ts` and add the following boilerplate.

```typescript
import { Component, Vue } from 'vue-property-decorator';
import WithRender from './to-do.html';

@WithRender
@Component
export default class ToDo extends Vue {

}
```

Also, create a new file in the same directory called `to-do.html`. Here is some very basic HTML to get us started.

```html
<div>
    <h1>My To-Do App!</h1>
    <form>
        <input type="text" placeholder="Enter task...">
        <button type="submit">Add Task</button>
    </form>
</div>
```

Finally, we need to update our `Home.vue` file within the views directory so that it loads our new `ToDo.ts` component. Update the component to look like this.

```vue
<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png">
    <ToDo />
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import ToDo from '@/components/ToDo.ts';

@Component({
  components: {
    ToDo,
  },
})
export default class Home extends Vue {}
</script>
```

## Our First Component
The first thing I would like to do is abstract our form to its own component. Let's create a new component called `ToDoForm.ts` and copy the HTML template also. We should have two new files that look like this.

**ToDoForm.ts**
```typescript
import { Component, Vue } from 'vue-property-decorator';
import WithRender from './to-do-form.html';

@WithRender
@Component
export default class ToDoForm extends Vue {

}
```

**to-do-form.html**
```html
<form>
    <input type="text" placeholder="Enter task...">
    <button type="submit">Add Task</button>
</form>
```

Now that we have abstracted the component, we need to include this child component within our parent. In order to do this, we need to import the component into the TypeScript file, register it and then update the template to display it. Let's go through that now. To get started, import the `ToDoForm.ts` component into the `ToDo.ts` component.

```typescript
import ToDoForm from './ToDoForm';
```

Next, we need to register the component. We can do this by passing an object to our `@Component` decorator. Here we can configure the component like we would any normal Vue component.

```typescript
@Component({
  components: {
    'to-do-form': ToDoForm
  }
})
```

Our `ToDo.ts` file should now look like this:

```typescript
import { Component, Vue } from 'vue-property-decorator';
import WithRender from './to-do.html';
import ToDoForm from './ToDoForm';

@WithRender
@Component({
  components: {
    'to-do-form': ToDoForm
  }
})
export default class ToDo extends Vue {

}
```

The final step is to now update our `to-do.html` template so that we are referencing the child component. Simply remove the form and replace it with a `<to-do-form />` tag. Once done, our template file should look like this.

```html
<div>
    <h1>My To-Do App!</h1>
    <to-do-form />
</div>
```

You should now see that the form is being displayed on our page.

##Showing The Tasks
The next thing we are going to tackle is showing the tasks to the user. To start with we need to store the tasks as an array. In our `ToDo.ts` component, we will add a new property. This is essentially the same as adding any data property in a standard `.vue` component.

Let's define a type for a task. Create a `types` folder within `src` and then create a file called `Task.ts`. Our task is going to be relatively simple. Each task will consist of a description and a completion status. Here is our type definition.

```typescript
type Task {
  completed: boolean;
  description: string;
}

export default Task;
```

Now we can create our data property in our `ToDo.ts` component. Import the type into our component...

```typescript
import Task from '@/types/Task';
```
...and then add the following property to the class.

```typescript
public tasks: Task[] = [];
```
In order to see the results on the page, we need to render them using a `v-for` loop. Here you can see I have updated the template within the `to-do.html` template to output each task item in an unordered list.

```html
<div>
    <h1>My To-Do App!</h1>
    <to-do-form></to-do-form>
    <ul>
        <li v-for="task in tasks"><input type="checkbox" :checked="task.completed"> {{ task.description }}</li>
    </ul>
</div>
```
For now, let's hard-code some tasks so we know that it is working. Update the tasks property in the `ToDo.ts` component to have some items in the array by default.

```typescript
public tasks: Task[] = [
  { description: 'Make Coffee', completed: false },
  { description: 'Feed Dragons', completed: false },
];
```

You should now see those tasks being rendered on the page. Good job!

##Creating New Tasks
So we now have a form and a way of displaying the current tasks on the page. Next, we need to actually add the functionality so that when a user adds a task in the form it updates the data property.

To do this we will first need to edit our `ToDoForm.ts` component. We need to use `v-model` so that we can capture the input with data binding. Within your `ToDoForm.ts` file, add a new property called task.

```typescript
public task: string = '';
```

Now update the `to-do-form.html` template so that the input field has `v-model`.

```html
<form>
    <input type="text" placeholder="Enter task..." v-model="task">
    <button type="submit">Add Task</button>
</form>
```

Great, we now have a way of capturing the user input. Next, we need to ensure that when the "Add Task" button is clicked, we emit an event to the parent component. Let's add an event listener to the form tag within our template.

```html
<form @submit.prevent="emitTask">
    <input type="text" placeholder="Enter task..." v-model="task">
    <button type="submit">Add Task</button>
</form>
```

Next, we need to create the `emitTask` method on in our `ToDoForm.ts` component. Simply add a new method to the class. Within this method we want to emit a new event, passing the value entered in the form. We then want to reset the value ready for the next input.

```typescript
public emitTask(): void {
  this.$emit('added', this.task);
  this.task = '';
}
```

Now that we have an event emitting, we can listen for this event in our parent component. First, let's add an event listener to the component in our `to-do.html` template file.

```html
<to-do-form @added="addTask" />
```

Now we need to create the listener. Add a new method to the `ToDo.ts` class called `addTask`. In this method, we want to push a new item to the tasks property with the description from the event.

```typescript
public addTask(description: string): void {
    this.tasks.push({ description, completed: false });
}
```

And now you are ready to check it out in the browser. You should now be able to add a new task, submit the form and see it added to the list below.

##What About Props?
So far I have shown how to use child components, data properties, and events. But you'll commonly have to use props in any real-world use case.

Let's add a new feature so that we can easily customise the text of the form button. We want to be able to pass the value through a prop called `button-text`.

Firstly, we'll update the template so we are passing a prop through to the child component.

```html
<to-do-form @added="addTask" button-text="Create Task" />
```

The next thing we need to do is accept the prop in our child component. In order to do this, we are going to use another decorator, `@Prop`. Update the import statement within our `ToDoForm.ts` file so that we can use the decorator.

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

Now we can go ahead and use it. Add the decorator to the class to accept the prop. Your `ToDoForm.ts` file should now look like this.

```typescript
import { Component, Vue, Prop } from 'vue-property-decorator';
import WithRender from './to-do-form.html';

@WithRender
@Component
export default class ToDoForm extends Vue {
  
  @Prop(String) readonly buttonText!: string

  public task: string = '';

  public emitTask(): void {
    this.$emit('added', this.task);
    this.task = '';
  }
}
```
You'll notice that for the prop decorator, we are declaring the type twice. Let me explain just a little what is going on here. The first time we specify it, we are passing it as a parameter to the decorator. This is for Vue's type checking. This is similar to how you would declare the property in the following way.

```javascript
buttonText: {
  type: String
}
```

We also specify the type at the end of the property. This is for TypeScript's type checking.

We should now be able to update our `to-do-form.html` template to reference the property as opposed to a hard-coded value.

```html
<form @submit.prevent="emitTask">
    <input type="text" placeholder="Enter task..." v-model="task">
    <button type="submit">{{ buttonText }}</button>
</form>
```

However, what if we don't pass through a property? You'll notice that we will just get an empty button. Let's add a default just to be safe. In order to do this, we need to pass more information to the `@Prop` decorator. As we are already providing the type, we instead need to pass in an object so that we can configure multiple options.

```typescript
@Prop({ type: String, default: 'Add Task'}) readonly buttonText!: string
```

That is all there is to it. We now have a default value for the button text if we don't provide one.

## Conclusion
This has been somewhat of a lengthy article, but I hope it has been useful. We have looked at how we can use child components, data properties, events, and props.

If you have any questions at all, please feel free to get in touch and I'll do my best to answer them.

If you would like to view the code for this, I have pushed it to a git repository. You can find it here - [https://github.com/georgehanson/vue-todo-typescript](https://github.com/georgehanson/vue-todo-typescript)

##Homework
If you fancy it, here is a little task for you to do.

Firstly fork the repository. Next add a feature so that you can mark the task as complete. But rather than using `v-model`, make each task it's own component and use events.

Good luck!