← Other topics

Vue.js Simplified
Single File Components (#13)

Video Notes

In Part 12 of this series, we saw our first example of a Vue Single File Component (SFC) via the file src/App.vue.

The idea with Single File Components is to organize together all the JavaScript, HTML, and CSS of a specific entity or component of your application into one file. Within the JavaScript, you will define all the Vue options (data, computed properties, methods, watchers, etc.) necessary for that component, and each component will be set up as its own Vue instance.

Here’s a diagram for a generic site so you can visualize how things might be broken down into components:

Two key benefits of SFCs include:

  1. Ease of development - rather than hunting around three separate files (JS, HTML, CSS) to manage a specific aspect of your application, you can have everything organized together into one central location.
  2. Reusability - Single File Components can be imported and reused as needed throughout your application. We can see this imagined in the above diagram with the repeating of the NotificationItem and SearchResult components.

Creating a WordCard component for FlashWord

To practice with components, let’s adapt our FlashWord code so that each word card on the page is built via a component:

To do this, create a new file Single File Component /src/components/WordCard.vue with the following code:

<script>
export default {
    name: 'WordCard',
    props: ['word'],
    methods: {
        checkAnswer() {
            // When referencing props, prefix with the `this` keyword
            this.word.correct = this.word.word_b == this.word.answer;

            if (this.word.correct) {
                // Emit the custom event `incrementCorrectCount` to the parent component that is utilizing this component
                this.$emit('incrementCorrectCount');
            }
        }
    }
}
</script>

<template>
    <div class='card' v-bind:class='{ correct: word.correct}'>
        <p class='word'>{{ word.word_a }}</p>
        <input
            v-if='!word.correct' 
            type='text' 
            v-model='word.answer' 
            v-on:keyup.enter='checkAnswer()'>

        <p v-else class='correctAnswer'>{{ word.answer }}</p>
    </div>
</template>

<style scoped>

/* The scoped attribute means that these styles will only apply to elements in this component */

.card {
    background-color: #E8F0FF;
    border-radius: 5px;
    padding: 10px 0;
    font-size: 25px;
}

input[type=text] {
    border: 0;
    font-size: 25px;
    border-radius: 5px;
    margin-top: 5px;
    text-align: center;
    padding: 5px;
}

.word {
    font-weight: bold;
    padding: 0;
    margin: 0;
}

.correctAnswer {
    padding: 0;
    margin: 0;
}

.correct {
    color: #0f5132;
    background-color: #d1e7dd;
}

</style>

Here are the important things to observe about the above code:

Key points

  • Props are how we can pass data ”down” from parent to child components
  • Events are how we can communicate information ”up” from child to parent components

Utilizing the WordCard component

With everything in WordCard set up, let’s turn our attention to App to utilize this component.

First, update the JavaScript in src/App.vue to import the new WordCard component:

import WordCard from './components/WordCard.vue'

And update the options to include a new component option that registers the WordCard component:

export default  {
    /* Register any components we will use in this component */
    components: {
        WordCard
    },
    /* The following options were abbreviated for brevity */
    data() {return {} },
    computed: {},
    watch: {},
    methods: {}
}

Additionally, update the App template to utilize the WordCard component within div#cards:

<div id='cards'>
    <WordCard
      v-for='word in shuffledWords'
      v-bind:word='word' 
      v-on:incrementCorrectCount="incrementCorrectCount"></WordCard>
</div>

Observations about the above code:

export default  {
    components: {
        WordCard
    },
    data() { return { /* Redacted for brevity */ } },
    computed: {  /* Redacted for brevity */ },
    watch: {  /* Redacted for brevity */ },
    methods: {
        incrementCorrectCount() {
            this.correctCount++;
        }
    }
}

Test it out

Load your application in the browser and test it out. It should work exactly as it did before, as we haven’t changed any of this apps functionality - only how it was built behind the scenes.

Concluding notes

← Other topics