Idris Lawal Idris is a software engineer, with backgrounds in computer networking and security. He can be reached here on Twitter: https://twitter.com/oluwaseunmi

Vue.js simplified: Components, props, and slots

8 min read 2425

The Vue logo against a background of a skyline.

In this article, we will be using Vue.js, Vue CLI and Bootstrap CSS.

Installations

To Install Vue.js (if you don’t already have it), run this command:

npm install vue

To install Vue CLI, run this:

npm install -g @vue/cli

Now, we can start a project (which we will call bookapp) with Vue CLI by running this command:

vue create bookapp

And our project directory looks like this:

|-- node_modules/
|-- public/
|-- src/
   |-- assets/
   |-- components/
   |-- App.vue
   |-- main.js
|-- .gitignore
|-- babel.config.js
|-- package.json
|-- README.md
|-- yarn.lock

To install Bootstrap CSS, run:

npm install bootstrap@4.0.0

Then, we can import it in the main.js file, like this:

import 'bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';

What are components?

“Components are a collection of elements that are encapsulated into a group that can be accessed through a single element.” – Sarah Drasner

Components help us make a chunk of code reusable. This means whenever we need a functionality which has previously been implemented, we do not need to repeat the same chunk of code over again to replicate such functionality.

For this article, we will focus on making single file components.

We made a custom demo for .
No really. Click here to check it out.

Slots and props help us achieve this by making it easy to dynamically change the content of components.

What are props?

In simple terms, props are openings within a component that are filled with data. This means that when a component has a prop, it expects to get data from another component or view within which it is imported. This other component could be called the parent component.

Using our app created earlier, bookapp, we will make the app such that we can have a listing of books (in cards). Each card will be a component that will be reusable within the books listing.

So, let’s say we create a component called BookList.vue and within that, we import another component called BookCard.vue. BookList.vue will be known as a parent component. Hence, BookCard.vue will be known as the child component.

Check out the code example below:

<!--BookList.vue-->
<template>
    <div class="container">
        <div>
            <div class="row">
                <div class="col-md-4">
                    <BookCard />
                </div>
            </div>
        </div>
    </div>
</template>

<script>
import BookCard from './BookCard.vue'

export default {
    components: {
        BookCard
    },
</script>

So our directory structure of the src/ folder should look like this:

|-- src/
   |-- assets/
   |-- components/
       |-- BookCard.vue
       |-- BookList.vue
   |-- App.vue
   |-- main.js

Props are built for passing data from parent to child. They are designed for one-way data flow.

Let’s see props in action using our earlier example of BookList.vue and BookCard.vue.

Within BookCard.vue, let’s say we want to accept a prop which we will call bookData that will contain information about a particular book.

<!--BookCard.vue-->
<template>
    <div>
        <div class="card">
            <img :src="bookData.img_url" class="card-img-top">
            <div class="card-body">
                <p class="card-text">Title: {{bookData.title}}</p>
                <p class="card-text">Author: {{bookData.author}}</p>
                <p class="card-text">Description: {{bookData.description}}</p>
                <slot></slot>
                <slot name="button"></slot>
            </div>
        </div>
    </div>
</template>

<script>
export default {
    props: {
        bookData: {
            type: Object,
            required: true,
            default: () => {}
        }
    }
}
</script>

Notice how the prop is defined. The type and required properties are used for prop validation. Props can also have default values as shown above. String and numbers can be directly passed as is. However, arrays and object default values must be passed as a function.

If validation isn’t necessary, we could also have simply done this:

<script>
export default {
    props: ['bookData']
}
</script>

Now that we have modified the child component (BookCard.vue) to accept a prop named bookData. We will now have to pass that data to the child component from the parent component, which in this case is BookList.vue.

First, we need to have a list of items (data) we want to pass to the component. This data could be from a database or an API. For the purpose of this writing I will hard code some book data.

<!--BookList.vue-->
<script>
import BookCard from './BookCard.vue'

export default {
    components: {
        BookCard
    },
    data(){
        return {
            books: [
                {
                    'author': 'David Deustch',
                    'title': 'The beginning of Infinity',
                    'description': 'A book on philosphy about the origins of man',
                    'img_url': 'https://placeimg.com/640/480/any'
                },
                {
                    'author': 'Paul Coelho',
                    'title': 'The Alchemist',
                    'description': 'The true search for one\'s treasure',
                    'img_url': 'https://placeimg.com/640/480/any'
                },
                {
                    'author': 'Susan Kaye Quinn',
                    'title': 'Open Minds',
                    'description': 'Sci-Fi Futuristic book about mind reading',
                    'img_url': 'https://placeimg.com/640/480/any'
                },
                {
                    'author': 'Robert Kiyosaki',
                    'title': 'Rich Dad, Poor Dad',
                    'description': 'Motivational book on wealth building',
                    'img_url': 'https://placeimg.com/640/480/any'
                },
                {
                    'author': 'Dan Brown',
                    'title': 'The Da Vinci Code',
                    'description': 'Conspiracy theories about secrets of the holy grail',
                    'img_url': 'https://placeimg.com/640/480/any'
                },
                {
                    'author': 'Arthur Hailey',
                    'title': 'The money changers',
                    'description': 'Travel back in time and experience the banking system',
                    'img_url': 'https://placeimg.com/640/480/any'
                },
            ]
        }
    }
}
</script>

On the markup side of things, all we need to do is iterate over the data with a v-for and pass in each object to the child component (BookCard) via the prop. Let’s see how that is done.

Note: In Vue.js, components named in camelCase or PascalCase are auto-converted to kebab-case in the markup. So instead of writing <BookCard/>, we can write <book-card/>.

This is also the case for props. While we have named our prop as bookData, we can reference it in markup as book-data:

<!--BookList.vue-->
<template>
    <div class="container">
        <div>
            <div class="row">
                <div class="col-md-4" v-for="(book, i) in books" :key="i">
                    <book-card :book-data="book" />
                </div>
            </div>
        </div>
    </div>
</template>

Finally, to be able to view this simple app we have built, we will call the BookList component in the App.vue file, like this:

<!--App.vue-->
<template>
  <div id="app">
    <book-list/>
  </div>
</template>

<script>
import BookList from './components/BookList.vue'

export default {
  name: 'App',
  components: {
    BookList,
  }
}
</script>

Now if we view our app, it should look similar to this:

Our book.app example with images and book information.

Each card is the same component, but contains different content loaded dynamically.

Props passed to components only need to be bound to the component when the data passed is expected to change dynamically. This means that we passed the books prop by binding that data to prop, like this:

:book-data="book"

The binding there is the colon (:) sign (which is a short form for v-bind:) that is added to the prop book-data.

If in a situation where we do not need the data to change dynamically, then there will be no need for the binding. We could just say book-data="book" and this will pass the String “book” to the prop and not an object.

But our prop validation will throw errors in the browser console to indicate that an object is what is expected and not a string.

What are slots?

In simple terms, slots are openings within a component similar to props. Unlike props, they not only take data, but can also take other markups or even other components.

To make it clearer, their major difference is that with props, the parent can only pass the data down to the child without any control over how it will be rendered. But with slots, the parent can determine exactly how the data should be rendered, or even pass down another component.

Let’s continue with our bookapp project.

We have all the books’ information rendered in cards, but we want to add a button to each of them for viewing more information about each book. To achieve this, we could decide to open a slot on a the BookCard component like this:

<!--BookCard.vue-->
<template>
    <div>
        <div class="card">
            <img :src="bookData.img_url" class="card-img-top">
            <div class="card-body">
                <p class="card-text">Title: {{bookData.title}}</p>
                <p class="card-text">Author: {{bookData.author}}</p>
                <p class="card-text">Description: {{bookData.description}}</p>
                <slot></slot> <!--Slot opening-->
            </div>
        </div>
    </div>
</template>

Now from our parent component (BookList.vue), we can pass what we want to the slot of the child component:

<template>
    <div class="container">
        <div>
            <div class="row">
                <div class="col-md-4" v-for="(book, i) in books" :key="i">
                    <book-card :book-data="book">
                        <template v-slot> <!--Accessing slot-->
                            <button class="btn-primary">Read more</button>
                        </template>
                    </book-card>
                </div>
            </div>
        </div>
    </div>
</template>

Our button doesn’t have any link or action, but it should now be visible on the interface:

The buttons part of our book information app.

Named slots

In the earlier slot example, we used a <slot></slot> without any name. So when we referenced it in the parent component, we only did <template v-slot></template> without specifying any name.

In a situation where we need more than one slot, naming slots will come in handy.

Using our example project, let’s add another slot for another button, which we will call the preview button:

<!--BookCard.vue-->
<template>
    <div>
        <div class="card">
            <img :src="bookData.img_url" class="card-img-top">
            <div class="card-body">
                <p class="card-text">Title: {{bookData.title}}</p>
                <p class="card-text">Author: {{bookData.author}}</p>
                <p class="card-text">Description: {{bookData.description}}</p>
                <slot></slot> <!--Slot opening-->
                <slot name="preview"></slot>
            </div>
        </div>
    </div>
</template>

Then, in the parent component (BookList.vue), we can pass another button. Only this time, with a name.

<template>
    <div class="container">
        <div>
            <div class="row">
                <div class="col-md-4" v-for="(book, i) in books" :key="i">
                    <book-card :book-data="book">
                        <template v-slot>
                            <button class="btn-primary">Read more</button>
                        </template>
                        <template v-slot:preview> <!--Accessing the preview slot-->
                            <button class="btn-secondary">Preview</button>
                        </template>
                    </book-card>
                </div>
            </div>
        </div>
    </div>
</template>

Now we have this:

Our app with a preview button now visible.

We accessed the slot using <**template** v-slot:preview>. We could also use the short form of it like <**template** #preview>. This is only possible with named slots, which is a new addition to Vue 2.6 and higher.

Scoped slots

While props are made for one way data flow, with scoped slots we can also pass data from the child back to the parent.
Let’s see how this is done.

Using our earlier example project, let’s say we have a computed property which we will name previewSlug inside of the BookCard component. This slug needs to be passed to our preview button as an href link. Let’s see how this can be achieved with scoped slots.

In the child component (BookCard.vue), we have included some new properties: methods and computed. The method properties allow us define a function, and the computed properties allowsus to have dynamic values.

In this case, we have a computed property: previewSlug, which we want to pass over to the parent component.

<!--BookCard.vue-->
<script>
export default {
    props: {
        bookData: {
            type: Object,
            required: true,
            default: () => {}
        }
    },
    methods: {
        computePreviewSlug(){
            return this.bookData.title.split(' ').join('-') +'-'+ this.bookData.author.split(' ').join('-')
        },
    },
    computed: {
        previewSlug(){
            return this.computePreviewSlug()
        },
    }
}
</script>

Now, from the named slot, we can bind a new property like this:

<!--BookCard.vue-->
<template>
    <div>
        <div class="card">
            <img :src="bookData.img_url" class="card-img-top">
            <div class="card-body">
                <p class="card-text">Title: {{bookData.title}}</p>
                <p class="card-text">Author: {{bookData.author}}</p>
                <p class="card-text">Description: {{bookData.description}}</p>
                <slot></slot> 
                <slot name="preview" :previewSlug="previewSlug"></slot>
<!--Binding previewSlug Property to Slot-->
            </div>
        </div>
    </div>
</template>

This will make the previewSlug data available in the parent component. We can immediately use it like this:

<!--BookList.vue-->
<template>
    <div class="container">
        <div>
            <div class="row">
                <div class="col-md-4" v-for="(book, i) in books" :key="i">
                    <book-card :book-data="book">
                        <template v-slot>
                            <button class="btn-primary">Read more</button>
                        </template>
                        <template v-slot:preview="slotProps"> 
                          <a :href="slotProps.previewSlug"><button class="btn-secondary">Preview</button></a>
                          <!--Using the previewSlug data by accessing the slot prop-->
                        </template>
                    </book-card>
                </div>
            </div>
        </div>
    </div>
</template>

Here we are binding the previewSlug to the href property. This will then add the unique slug link to our preview button.
Clicking the button should reveal the link, like this one:

Localhost-8080

Conclusion

Reusability is standard practice in coding and makes our code a lot cleaner and easier to maintain. Vue.js components helps us achieve this by giving us an avenue for dynamic data bindings using props and slots.

Here is a link to the Github repository for the dummy project we used in this article: https://github.com/IDTitanium/dummy-bookapp

Experience your Vue apps exactly how a user does

Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket. https://logrocket.com/signup/

LogRocket is like a DVR for web apps, recording literally everything that happens in your Vue apps including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.

The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error, and what state the application was in when an issue occurred.

Modernize how you debug your Vue apps - .

Idris Lawal Idris is a software engineer, with backgrounds in computer networking and security. He can be reached here on Twitter: https://twitter.com/oluwaseunmi

One Reply to “Vue.js simplified: Components, props, and slots”

Leave a Reply