Vue 2 With TypeScript – Part 2 – Your First Component

THE ITEMS LIST

Let’s now pretend we have been giving requirements for our app to have a component that simply displays a list of “items”. We are going to keep this simple initially and as we move towards more advanced chapter expand on it to demonstrate how we can better structure our application in order to support:

  • Quick prototyping and development using mocked data
  • Component Organization
  • Unit Testing
  • State Management with Vuex
  • Internationalization support so we can render our user interface using different languages
  • Localization for number and date formatting for different cultures

ItemsList Component Requirements

Your initial version of the ItemsList component, will have to implement the following requirements (later, in more advanced chapters, we will expand on these as we get into more advanced topics):

  • The component will display a list of items
  • An item will have 3 properties:
    1. id
    2. name
    3. selected
  • The item name will be displayed to the user
  • The user should be able to select/deselect one or more item
  • An icon will be shown next to the name to indicate if the item is selected

ItemsList Component Code

Within the src/components directory, create a sub-directory called items. Within this folder add a new file called ItemsList.component.vue

Your directory structure will now look like this:

Within the ItemsList.component.vue file, paste the following code:

<template>
    <div>
        <h3>My Items:</h3>
        <ul>
            <li v-for="item in items" :key="item.id">
                {{ item.name }}
            </li>
        </ul>
    </div>
</template>

<script lang="ts">
    import { Component, Prop, Vue } from 'vue-property-decorator'
    @Component
    export default class ItemsListComponent extends Vue {
        @Prop() items!: any[]
    }
</script>

A few things to notice here. First, we specify the lang attribute on the <script> element with the value ts so we can use TypeScript. We also make use of class-style syntax. For our html template, we added a <h3> element with hard-coded tests just saying “My Items”. Then a <ul> with a v-for binding that will render all our items within <li> elements. 

Within the <script> section, we import the @Component and @Prop decorators. Also a reference to Vue so we can extend it in our class declaration. 

We then declare the items property as an array of any* for now.

@Prop() items!: any[]

* With ‘any’, TypeScript does not enforce type-checking on a property or variable. However, this is considered a bad practice as we lose the main benefit of TypeScript. There might be exceptions to this rule when using older 3rd party packages/libraries/plugins that do not offer type definitions. However, even in those cases it would be strongly recommended to provide interfaces and types so that you can still avoid using ‘any’.

Home View

Within the src/views directory, open the existing Home.vue file. This is the existing code that was stubbed by the vue-cli when we first created the project:

<template>
    <div class="home">
        <img alt="Vue logo" src="../assets/logo.png">
        <HelloWorld msg="Welcome to Your Vue.js App"/>
    </div>
</template>

<script>
    // @ is an alias to /src
    import HelloWorld from '@/components/HelloWorld.vue'
    export default {
        name: 'Home',
        components: {
            HelloWorld
        }
    }
</script>

Remove the highlighted parts in red: the reference to the HelloWorld component within the <template>; the <img> element; clear the code within the <script> section. Also add the attribute lang=”ts” to the <script> section as we will be using TypeScript:

<template>
    <div class="home">
    </div>
</template>

<script lang="ts">
</script>

Now, similarly to how we did in our custom ItemsList.component.vue, lets import the @Component and @Prop decorators and start implementing our Home view (new and modified code is highlighted in yellow).

Then import a reference to the ItemsList.component.vue and register it through our @Component decorator.

For now, also quickly mock some data for our list of items that we’ll feed to our ItemsComponent. For this we add a private property to the Home class called items and initialize it with some hard-coded data*.

Finally, we add an <ItemsComponent> element within the <template> markup. We can insert it within the <div class=”home”> element for now. Add an attribute called :items to our <ItemsComponent>. This is how you do one-way binding in Vue. The Home items property with the hard-coded data is fed into the component items property this way. The complete code within the Home.vue file should not look like this:

<template>
    <div class="home">
        <ItemsListComponent :items="items" />
    </div>
</template>

<script lang="ts">
    import { Component, Prop, Vue } from 'vue-property-decorator'
    import ItemsListComponent from '@/components/items/ItemsList.component.vue'
    @Component({
        components: {
            ItemsListComponent
        }
    })
    export default class Home extends Vue {
        private items: any[] = [{
            id: 1,
            name: 'Item 1',
            selected: false
        }, {
            id: 2,
            name: 'Item 2',
            selected: false
        }, {
            id: 3,
            name: 'Item 3',
            selected: false
        }]
    }
</script>

* Note: using hard-coded data is a bad practice and here we are only doing it to first illustrate how things flow, and later in the next chapters will remove in favor of best practices and patterns (see Chapter 5)

Now, the web browser will refresh and display our preliminary items list being rendered more or less like this:

Recap 

What We Learned

  • How to create a basic component that displays a list of items
  • How to consume that component in a view

Observations

  • The items property within the ItemsList.component.vue is declared as an array fo type any
  • The Home.vue view contains hard-coded data (items) which is also declared as an array of any
  • This means that we are not leveraging strong-type checking at development time using TypeScript interfaces/models/types

Based on these observations, there are a few improvements that will be making into the next chapter:

Improvements

  • Create a TypeScript interface called IItem for enforcing type checking at development time for our items data
  • Update our code so that it uses the new IItem interface