Telerik blogs
VueT2 Dark_1200x303
In our previous adventure, we looked at Vue’s basic slots. Scoped slots allow us to expose a piece of data, through the scope, to the parent that is using it.

In the last part of this article, we looked at the most basic form of Vue’s <slot>. We learned how to create basic components that allow passing any sort of structure and data into them, and we took a look at how to create multi-slot components.

This time, we are going to look at the basic <slot>'s amped-up sister, the scoped slot.

The Magic of Scoped Slots

Imagine that you are building a Pokemon card game, and you want to have a <Card> component that has some default slots for what is being displayed in the card. But you also want to give control to the parent of the information that is rendered in this space, for example, on the main content area of the card.

You may be thinking, easy, I just set a default content inside the slot in <Card>, and then override it on the parent, which is exactly where I want your mindset to be—Pokemon. You are stuck inside a v-for loop through an array of data. How are you going to handle an event that changes that default content? Are you going to capture the current Pokemon in the loop and store it in a variable? Pass it to a method?

Scoped slots allow us to expose a piece of data, through the scope, to the parent that is using it. Imagine the following:

  1. You create the <Card> component and you give it a pokemon internal state.
  2. This <Card> makes a random call to the API and fetches a Pokemon for itself.
  3. Your <Card> exposes a name slot that is being defaulted to the Pokemon’s name.
  4. You want the parent to be able to override this information, but you don’t know what the internal of the card is. We are going to expose this information through a scoped slot.
  5. Now that your scoped slot exposes the pokemon, your parent can grab it and use it as needed.

Building the Demo Card

In order to better understand how scoped slot works, let’s create the card in the above example. We are going to use the Pokemon API for this!

We are going to create a better named card called <PokeCard>. The basic code for it will be the following.

<template>
  <div>{{ pokemon.name }}</div>
</template>

<script>
import axios from "axios";
export default {
  data() {
    return {
      pokemon: null
    };
  },
  created() {
    axios.get(
      `https://pokeapi.co/api/v2/pokemon/${Math.round(Math.random() * 150)}`
    ).then(result => {
      this.pokemon = result.data;
    });
  }
};
</script>

We are importing Axios because we are going to use it as our go-to library to make the async call to the API endpoint.

Next, in the created method, we use Axios’ get method to make a call to the endpoint of the PokeAPI that will return a Pokemon’s data. If you want to look at the documentation for this endpoint, you can visit the official page here.

This get method for Axios returns a JavaScript Promise. I’m not going to go into depth on how these work, but if you want to freshen up, here’s the link to the MDN page on Promises.

In the then block of the Promise, we are capturing the result of the call. Note that axios will wrap this result in an object of its own, so we need to access the information through the data property. This, in return, will hold the information that the API is giving us—that is, the actual Pokemon’s data.

Finally, we are simply dumping the [pokemon.name](http://pokemon.name) into view for now.

Go to your App.vue or wherever you are going to render this, and let’s create a loop to showcase the Card.

<template>
  <div id="app">
    <PokeCard :key="i" v-for="i in 20"/>
  </div>
</template>

<script>
import PokeCard from "./components/PokeCard";

export default {
  name: "App",
  components: {
    PokeCard
  }
};
</script>

<style>
#app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

Don’t forget to add the :key attribute! If you want a refresher on what key is and why it’s super important, you can check out my article on key here.

The v-for loop in the previous example will render 20 different <PokeCard>s in the screen, feel free to adjust as needed. Once you load this into your browser, you should see 20 Pokemon names pop up. Neat!

Making it “Pretty”

I say “pretty” in between quotes because my design skills are about as good as my cooking. Proceed at your own risk, and order pizza.

After some fiddling, I came up with the following for our beautiful PokeCard. Feel free to make this a work of art and show me how it’s done at @marinamosti. :D

<template>
  <div class="card">
    <div class="name">{{ pokemon.name }}</div>
    <div>
      <img :src="pokemon.sprites.front_default">
    </div>
    <div class="types">
      <ul>
        <li v-for="type in pokemon.types" :key="type.slot">{{ type.type.name }}</li>
      </ul>
    </div>
  </div>
</template>

<script>
import axios from "axios";
export default {
 data() {
    return {
      pokemon: null
    };
  },
  created() {
    axios
      .get(
        `https://pokeapi.co/api/v2/pokemon/${Math.round(Math.random() * 150)}`
      )
      .then(result => {
        this.pokemon = result.data;
      });
  }
};
</script>

<style lang="scss" scoped>
.card {
  border: 1px solid black;
  border-radius: 10px;
  margin: 0 auto;
  margin-bottom: 2rem;
  display: inline-block;

  .name {
    text-transform: capitalize;
    padding: 2rem 0;
  }

  .types {
    ul {
      margin: 0;
      padding: 0;
      list-style: none;
    }
  }
}
</style>

I’ve added some <style>s to the card, and in the template some markup to display the image and types for our Pokemon.

Bringing in the Big Water Guns

Time to start scoping this! Let’s add a regular named slot as we saw in the last article first. I want to keep the name and the image intact, but give the user of the component the ability to modify the contents of what is showing below the image.

<template>
  <div class="card">
    <div class="name">{{ pokemon.name }}</div>
    <div>
      <img :src="pokemon.sprites.front_default">
    </div>

    <slot name="content">
      <div class="types">
        <ul>
          <li v-for="type in pokemon.types" :key="type.slot">{{ type.type.name }}</li>
        </ul>
      </div>
    </slot>

  </div>
</template>

I’ve wrapped the div.types content all with a named <slot> called content. This will allow for all this part to be overwritten by the parent.

Let’s go back to App.vue (or wherever you are rending this list) and make a small adjustment so that every “odd” card has the content replaced.

<PokeCard :key="i" v-for="i in 20">
  <template v-slot:content v-if="i % 2">
		This is a normal slot.<br/>How do I get the data?
	</template>
</PokeCard>

Sweet! We’ve added a <template> that declares a v-slot: with the name content, so anything we put in here is going to overwrite what we currently have as the “types” list.

Now, I want to be able to overwrite this in the parent as a list of the Pokemon’s moves! Except … how? The data for the Pokemon is inside the card. 🤔

Enter Scoped Slots

For cases such as these where we need to expose a piece of data from the child to the parent through a slot, we have what are called scoped slots. I’ve seen a lot of people struggling with this concept, so hopefully with this very simple and dumb example you will be able to grasp the concept, since technically it won’t be challenging to do!

We need to expose or bind the pokemon property to this slot first, so that it is “shown” to the parent.

<slot name="content" v-bind:pokemon="pokemon">
   [...]  
</slot>

Update your <slot> inside PokeCard.vue to v-bind:pokemon to the pokemon internal state. You can also use the short syntax :pokemon="pokemon".

What this is doing is literally binding that data into the slot. Think about the slot as a box, and right now we are putting these variables in the box. Whoever wishes to use this box (the parent) can make use of these internal variables!

Now head over to App.vue and let’s make some small adjustments.

<PokeCard :key="i" v-for="i in 20">
 <template v-slot:content="props" v-if="i % 2">
		{{ props.pokemon.name }}
	</template>
</PokeCard>

I’ve gone ahead and added a little bit of syntax to the v-slot:content declaration. You can see that it now has a second part ="props". What exactly does this mean?

What is means, literally, is:

“This slot (v-slot) named content (:content) will receive an object named props (="props") with some data that you can use.”

Now, check the line that follows inside the <template>. We are accessing the name of the Pokemon by first looking inside the props object, then inside the pokemon property of this object, finally we find the name and display it inside the template.

What can you find inside this object you ask? Anything and everything your component declared as a binding inside the <slot>! Remember when we did this?

<slot name="content" v-bind:pokemon="pokemon">

Well, that :pokemon="pokemon" is EXACTLY what you’re getting inside the props.pokemon object!

Show dem Moves

One more thing is left for our neat example. Right now we are only displaying the name of the Pokemon in the scoped slot, but we said earlier we wanted to show all the moves that it has instead of its types.

Let’s make some changes to our App.vue inside the v-slot:content declaration that lives within our <PokeCard>.

<PokeCard :key="i" v-for="i in 20">
  <template v-slot:content="props" v-if="i % 2">
    <ul style="margin: 0; padding: 0; list-style: none;">
      <li v-for="move in props.pokemon.moves.slice(0,3)" 
					:key="move.slot">
				{{ move.move.name }}
			</li>
    </ul>
  </template>
</PokeCard>

A couple of noteworthy things. The v-if declaration here is making it so that we only display this template on odd cards (1, 3, 5, etc.).

The <li> has a v-for in which we’re looping through the props.pokemon.moves object, but I’ve appended slice(0,3) to keep the array to a maximum of 3 items. Some of these little guys can learn a LOT of moves.

Finally we display the move's name inside the <li>. Go ahead into your browser and behold the awesome!

One Smol Thing

One last thing I want to mention before wrapping up the taco.

You may have seen in others’ code or articles that the v-slot for scoped slot syntax involves curly braces, like so.

<template v-slot:content="{pokemon}">

I didn’t want to confuse you earlier, so I left this little bit out. This is not special Vue syntax or magic, this is object destructuring. What is happening here is that inside the props object that we had before, we have a pokemon property, right?

Well, we are simply telling JavaScript to extract that property so we can use it directly. So instead of props.pokemon.moves, you would write pokemon.moves. Handy!

Object destructuring though is out of the scope of this article, so I won’t go into further detail.

Wrapping Up

The code for this article can be found in the following codesandbox:

https://codesandbox.io/s/pokecards-hnbph

Scoped slots is one of those things that can take a bit to wrap your head around, but once you Catch 'em, its a very powerful tool in your arsenal!

As always, thanks for reading and share with me your scoped slot adventures and favorite Pokemon on Twitter at: @marinamosti .

PS. All hail the magical avocado! 🥑
P.P.S. ❤️🔥🐶☠️


About the Author

Marina Mosti

Marina Mosti is a frontend web developer with over 18 years of experience in the field. She enjoys mentoring other women on JavaScript and her favorite framework, Vue, as well as writing articles and tutorials for the community. In her spare time, she enjoys playing bass, drums and video games.

Related Posts

Comments

Comments are disabled in preview mode.