Connect with us

Technology

A Complete Information to Vue Slots – SitePoint


Parts are the center of contemporary internet software improvement. Each app consists of numerous parts easily stitched collectively so as to work as a complete unit. These parts must be maximally versatile and reusable to permit for utilizing them in numerous conditions and even in numerous apps. One of many major mechanisms many frameworks use to fulfill such necessities — in partucular Vue — is known as a “slot”.

Slots are a robust and versatile content material distribution and composition mechanism. You’ll be able to consider slots as customizable templates (much like PHP templates, for instance) which you should utilize elsewhere, for numerous use instances, producing totally different results. For instance, in UI frameworks like Vuetify, slots are used to make generic parts corresponding to an alert part. In these sorts of parts, slots are used as placeholders for the default content material and any further/optionally available content material, corresponding to icons, photos, and so forth.

Slots can help you add any construction, fashion, and performance to a selected part. By utilizing slots, builders can drastically cut back the variety of props utilized in a single part, making parts a lot cleaner and manageable.

On this tutorial, we’ll discover how you can harness the facility of slots within the context of Vue 3. Let’s get began.

Primary Utilization of Slots

Principally, Vue affords two sorts of slots: a easy slot, and a scoped slot. Let’s begin with the straightforward one. Take into account the next instance:

const app = Vue.createApp({})

app.part('primary-button', {
  template: `
    <button>
      <slot>OK</slot>
    </button>`
})

app.mount('#app')

Right here, we’ve got a major button part. We wish the button’s textual content to be customizable, so we use the slot part contained in the button ingredient so as to add a placeholder for the textual content. We additionally need a default (fallback) generic worth in case we don’t present a customized one. Vue makes use of as default slot content material all the things we put contained in the slot part. So we simply put the textual content “OK” contained in the part. Now we are able to use the part like this:

<div id="app">
  <primary-button></primary-button>
</div>

See the Pen
Vue 3 Slots: Primary Slot
by SitePoint (@SitePoint)
on CodePen.

The result’s a button with textual content “OK”, as a result of we haven’t supplied any worth. However what if we wish to create a button with customized textual content? In that case, we offer customized textual content within the part implementation like this:

<div id="app">
  <primary-button>Subscribe</primary-button>
</div>

Right here, Vue takes the customized “Subscribe” textual content and makes use of it as an alternative of the default one.

As you may see, even on this easy instance, we get a large amount of flexibility over how we wish to current our part. However that is solely the tip of the iceberg. Let’s have a look at a extra advanced instance.

Constructing a Quote of the Day Part

Now, we’ll construct a quote part which shows the quote of the day. Right here’s the code:

const app = Vue.createApp({}) 

app.part('quote', {
  template: `
    <article>
      <h2>The quote of the day says:</h2>
      <p class="quote-text">
        <slot></slot>
      </p>
    </article>`
})

app.mount('#app')
<div id="app">
  <quote>
    <div class="quote-box">
      "Creativity is simply connecting issues."
      <br><br>
      - Steve Jobs
    </div>
  </quote>
</div>
.quote-box {
  background-color: lightgreen;
  width: 300px;
  padding: 5px 10px;
}

.quote-text {
  font-style: italic;
}

On this instance, we create a title heading whose content material will probably be fixed, after which we put a slot part inside a paragraph, whose content material will differ relying on the present day’s quote. When the part is rendered, Vue will show the title from the quote part adopted by the content material we put contained in the quote tags. Additionally take note of the CSS lessons used each within the quote creation and implementation. We will fashion our parts in each methods relying on our wants.

See the Pen
Vue 3 Slots: Quote Part
by SitePoint (@SitePoint)
on CodePen.

Our quote of the day part works wonderful, however we nonetheless must replace the quote manually. Let’s make it dynamic by utilizing the Fav Quotes API:

const app = Vue.createApp({   
  knowledge() {
    return {
      quoteOfTheDay: null,
      present: false
    };
  },
  strategies: {
    showQuote() {
      axios.get('https://favqs.com/api/qotd').then(end result => {
        this.quoteOfTheDay = end result.knowledge
        this.present = true
      }); 
    }
  }
})

...

app.mount('#app')
<div id="app">
  <quote>
    <button v-if="present == false" @click on="showQuote">Present Quote of the Day</button>
    <div v-if="present" class="quote-box">
      {{ quoteOfTheDay.quote.physique }} 
      <br><br>
      - {{ quoteOfTheDay.quote.writer }}
    </div>
  </quote>
</div>

Right here, we use Axios to make a name to the “Quote of the Day” API endpoint, after which we use the physique and writer properties, from the returned JSON object, to populate the quote. So we not want so as to add the quote manually; it’s achieved routinely for us.

See the Pen
Vue 3 Slots: Quote Part with Axios
by SitePoint (@SitePoint)
on CodePen.

Utilizing A number of Slots

Though a single slot might be fairly highly effective, in lots of instances this gained’t be sufficient. In a real-world state of affairs, we’ll usually want multiple single slot to do the job. Fortuitously, Vue permits us to make use of as many slots as we want. Let’s see how we are able to use a number of slots by constructing a easy card part.

Constructing a Primary Card Part

We’ll construct a card part with three sections: a header, a physique, and a footer:

const app = Vue.createApp({})

app.part('card', {
  template: `
    <div class="container">
      <header>
        <slot title="header"></slot>
      </header>
      <major>
        <slot></slot>
      </major>
      <footer>
        <slot title="footer"></slot>
      </footer>
    </div>`
})

app.mount('#app')
<div id="app">
  <card>
    <template v-slot:header>
      <h2>Card Header Title</h2>
    </template>

    <template v-slot:default>
      <p>
        Lorem ipsum leo risus, porta ac consectetur ac, vestibulum at eros. Donec id elit non mi porta gravida at eget metus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Cras mattis consectetur purus sit amet fermentum.
      </p>
    </template>

    <template v-slot:footer>
      <a href="#">Save</a> -
      <a href="#">Edit</a> -
      <a href="#">Delete</a>
    </template>
  </card>
</div>

In an effort to use a number of slots, we should present a reputation for every of them. The one exception is the default slot. So, within the above instance, we add a title property for the header and footer slots. The slot with no title supplied is taken into account default.

After we use the card part, we have to use the template ingredient with the v-slot directive with the slot title: v-slot:[slot-name].

See the Pen
Vue 3 Slots: Card Part
by SitePoint (@SitePoint)
on CodePen.

Be aware: the v-slot directive has a shorthand, which makes use of particular image # adopted by the slot’s title. So, for instance, as an alternative of v-slot:header, we are able to write #header.

Named slots will also be used with third-party parts, as we’ll see within the subsequent part.

Utilizing Named Slots with Bulma’s Card Part

Let’s take the Bulma’s Card part and tweak it somewhat bit:

const app = Vue.createApp({})

app.part('card', {
  template: `
    <div class="container">
      <div class="card">
        <header class="card-header">
          <slot title="header"></slot>
        </header>
        <major class="card-content">
          <slot></slot>
        </major>
        <footer class="card-footer">
          <slot title="footer"></slot>
        </footer>
      </div>
    </div>`
})

app.mount('#app')
.container {
  width: 300px;
}
<div id="app">
  <card>
    <template v-slot:header>
      <p class="card-header-title">
        Card Header Title
      </p>
    </template>

    <template v-slot:default>
      <p>
        Lorem ipsum leo risus, porta ac consectetur ac, vestibulum at eros. Donec id elit non mi porta gravida at eget metus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Cras mattis consectetur purus sit amet fermentum.
      </p>
    </template>

    <template v-slot:footer>
      <a href="#" class="card-footer-item">Save</a>
      <a href="#" class="card-footer-item">Edit</a>
      <a href="#" class="card-footer-item">Delete</a>
    </template>
  </card>
</div>

Right here, we use the lessons from the Bulma Card part as a base skeleton and add a slot for every part (header, content material, footer). Then, after we add the content material, all the things is structured correctly.

See the Pen
Vue 3 Slots: Card Part with Bulma
by SitePoint (@SitePoint)
on CodePen.

Utilizing Scoped Slots

We’ve taken a giant step ahead by utilizing a number of slots, however the true horse energy comes from scoped slots. They permit a mum or dad to get entry to the kid knowledge, which provides us many alternatives. Let’s see them in apply.

Constructing a Multipurpose Record Part

To exhibit the facility of scoped slots, we’ll construct a multipurpose listing part that can be utilized in numerous eventualities when we have to listing some knowledge:

const app = Vue.createApp({
  knowledge() {
    return {
      duties: ['Reading a book', 'Buying vegetables', 'Going for a walk']
    }
  }
})

app.part('listing', {
  props: ['items', 'name'],
  template: `
    <h3>{{ title }}</h3>
    <ul>
      <li v-for="merchandise in gadgets">
        <slot :merchandise="merchandise"></slot>
      </li>
    </ul>`
})

app.mount('#app')
<div id="app">
  <listing title="My duties listing:" :gadgets='duties' v-slot="{ merchandise: process }">
    <span>{{ process }}</span>
  </listing>
</div>

Right here, we create a listing part with gadgets and title props. The title will probably be used to provide a title to the listing and the gadgets will maintain the info we wish to listing. Within the template we add a title prop to a heading above the listing, after which we use the v-for directive to render every single merchandise. To reveal the info from the kid to the mum or dad we bind the merchandise as a slot attribute (<slot :merchandise="merchandise"></slot>).

Within the mum or dad we’ve got an array of duties. After we use the listing we offer a reputation prop, then bind the gadgets to the duties array and we use v-slot to get entry to the kid knowledge.

Be aware: all sure props in a slot are referred to as slot props, and we are able to expose them by utilizing v-slot:[slot-name]="slotProps", after which we use single prop like this: {{ slotProps.merchandise }}. However on this instance, I exploit object destructuring, which is extra elegant and direct technique to get the article properties. Additionally it lets you rename the article properties (as I did, renaming merchandise to process), which is extra versatile for various sorts of lists.

See the Pen
Vue 3 Slots: Multipurpose Record Part – Duties
by SitePoint (@SitePoint)
on CodePen.

The aim of our listing part is for use in numerous eventualities. So, let’s say we wish to use it as a buying listing. Right here’s how you can do it:

const app = Vue.createApp({
  knowledge() {
    return {
      merchandise: [
        {name: 'Tomatoes', quantity: '4'},
        {name: 'Cucumbers', quantity: '2'},
        {name: 'Red onion', quantity: '1'},
      ]
    }
  }
})

...

app.mount('#app')
<div id="app">
  <listing title="My buying listing:" :gadgets='merchandise' v-slot="{ merchandise: product }">
    <span>{{ product.amount }} {{ product.title }}</span>
  </listing>
</div>

Right here, we’ve got a listing of merchandise the place every merchandise has a title and a amount property. We use the listing part nearly identically as within the earlier instance, besides that right here the listing merchandise has two properties.

See the Pen
Vue 3 Slots: Multipurpose Record Part – Merchandise
by SitePoint (@SitePoint)
on CodePen.

As you may see, the listing part might be simply tailored to totally different itemizing use instances.

Slots vs Props

Earlier than studying about slots (and realizing their energy), many builders primarily use props for content material distribution. However when we’ve got a fancy part, the variety of props can improve drastically. In such instances, slots can exchange using props, making part implementation a lot clearer.

As an example the purpose made above, we’ll take an instance from Tailwind CSS, which makes use of solely props to create a vacation-card part:

const app = Vue.createApp({})

app.part('vacation-card', {
  props: ["https://www.sitepoint.com/vue-slots-comprehensive-guide/url", "https://www.sitepoint.com/vue-slots-comprehensive-guide/img", 'imgAlt', 'eyebrow', 'title', 'pricing'],
  template: `
    <div class="m-5 shadow-md w-80">
      <img class="rounded" :src="https://www.sitepoint.com/vue-slots-comprehensive-guide/img" :alt="imgAlt">
      <div class="p-2">
        <div>
          <div class="text-xs text-gray-600 uppercase font-bold">{{ eyebrow }}</div>
          <div class="font-bold text-gray-700 leading-snug">
            <a :href="https://www.sitepoint.com/vue-slots-comprehensive-guide/url" class="hover:underline">{{ title }}</a>
          </div>
          <div class="mt-2 text-sm text-gray-600">{{ pricing }}</div>
        </div>
      </div>
    </div>`
})

app.mount('#app')
<div id="app">
  <vacation-card
    url="/holidays/cancun"
    img="https://photos.unsplash.com/photo-1452784444945-3f422708fe5e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&match=crop&w=512&q=80"
    imgAlt="Seaside in Cancun"
    eyebrow="Non-public Villa"
    title="Stress-free All-Inclusive Resort in Cancun"
    pricing="$299 USD per evening"
  >
  </vacation-card>
</div>

See the Pen
Vue 3 Slots: Tailwind Primer with Props
by SitePoint (@SitePoint)
on CodePen.

As you may see, to offer all of the wanted knowledge/content material, this vacation-card part makes use of six props. This positively makes it reusable, but additionally laborious to keep up. We will produce aa a lot clearer and maintainable model by utilizing slots as an alternative of some props.

Let’s rewrite the part with slots:

const app = Vue.createApp({})

app.part('vacation-card', {
  props: ["https://www.sitepoint.com/vue-slots-comprehensive-guide/url", "https://www.sitepoint.com/vue-slots-comprehensive-guide/img", 'imgAlt'],
  template: `
  <div class="m-5 shadow-md w-80">
    <img class="rounded" :src="https://www.sitepoint.com/vue-slots-comprehensive-guide/img" :alt="imgAlt"> 
    <div class="p-2">
      <div>
        <div class="text-xs text-gray-600 uppercase font-bold"><slot title="eyebrow"></slot></div>
        <div class="font-bold text-gray-700 leading-snug">
          <a :href="https://www.sitepoint.com/vue-slots-comprehensive-guide/url" class="hover:underline"><slot title="title"></slot></a>
        </div>
        <div class="mt-2 text-sm text-gray-600"><slot title="pricing"></slot></div>
      </div>
    </div>
  </div>`
})

app.mount('#app')
<div id="app">
  <vacation-card
    url="/holidays/cancun"
    img="https://photos.unsplash.com/photo-1452784444945-3f422708fe5e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&match=crop&w=512&q=80"
    imgAlt="Seaside in Cancun"
  >
    <template v-slot:eyebrow>
      <p>Non-public Villa</p>
    </template>
    <template v-slot:title>
      <p>Stress-free All-Inclusive Resort in Cancun</p>
    </template>
    <template v-slot:pricing>
      <p><mark>$299 USD per evening</mark></p>
    </template>
  </vacation-card>
</div>

See the Pen
Vue 3 Slots: Tailwind Primer with Slots
by SitePoint (@SitePoint)
on CodePen.

Right here, we cut back the props to a few. We depart props just for the metadata and use named slots for the precise content material, which I feel is far more logical.

Listed here are my basic concerns about utilizing props vs slots.

Use props when:

  • the mum or dad can solely move the info right down to the kid part
  • the mum or dad has no management over how the info will probably be rendered and might’t customise the kid part
  • you could have an outlined design, the variety of variables is small, and the part is straightforward
  • you want to present metadata or some kind of configuration

Use slots when:

  • you want to move knowledge from the kid to the mum or dad
  • the mum or dad can decide how the info will probably be rendered
  • you wish to move not solely knowledge but additionally advanced HTML markup, a selected performance, and even different parts
  • you need the flexibility to customise the kid part from the mum or dad
  • you want extra flexibility to customise the part, the part is massive, and there are a lot of variables concerned

The underside line: for finest outcomes, mix props and slots! 😀

Exploring Extra Slots Use Circumstances

At this stage, it’s best to see the facility and adaptability of slots, however there are much more helpful methods to make use of them. Let’s discover them now.

Reusing Performance with Slots

Slots can ship not solely content material/construction but additionally performance. Let’s see this in motion:

const app = Vue.createApp({
  knowledge() {
    return {
      counter: 1
    }
  },
  strategies: {
    increment() {
      this.counter++
    }
  }
})

app.part('double-counter', {
  props: ['counter'],
  template: `
    <p>
      <slot :double="double"></slot>
    </p>`,
  computed: {
    double() {
      return this.counter * 2
    }
  }
})

app.mount('#app')
<div id="app">
  <double-counter :counter="counter">
    <template v-slot:default="{ double }">
      <h2>{{ counter }} x 2 = {{ double }}</h2>
      <button @click on="increment">
        Increment counter by 1
      </button>
    </template>
  </double-counter>
</div>

Right here, we create a double-counter part that can double the worth of a counter. To do that, we create a computed property double which we expose to the mum or dad by binding its worth as a slot attribute. The computed takes the worth of the counter prop and doubles it.

Within the mum or dad we’ve got a counter knowledge property and an increment() technique that increments it by one. After we use the double-counter, we bind the prop counter, then we expose the double computed property. Within the expression, we use the counter and double. After we click on the button, the counter is incremented by 1 and the computed property is recalculated with the brand new doubled worth. For instance, if the counter prop is ready to three, the doubled worth will probably be 6 (3 x 2 = 6).

See the Pen
Vue 3 Slots: Reusing Performance – Double Counter
by SitePoint (@SitePoint)
on CodePen.

We will make this part extra versatile. Let’s tweak it to multiply the counter by any customized worth:

const app = Vue.createApp({
  knowledge() {
    return {
      counter: 1,
      by: 4
    }
  },
  strategies: {
    increment() {
      this.counter++
    }
  }
})

app.part('multiply-counter', {
  props: ['counter', 'by'],
  template: `
    <p>
      <slot :multiply="multiply"></slot>
    </p>`,
  computed: {
    multiply() {
      return this.counter * this.by
    }
  }
})

app.mount('#app')
<div id="app">
  <multiply-counter :by="by" :counter="counter">
    <template v-slot:default="{ multiply }">
      <h2>{{ counter }} x {{ by }} = {{ multiply }}</h2>
      <button @click on="increment">
        Increment counter by 1
      </button>
    </template>
  </multiply-counter>
</div>

Right here, we add a prop, by, which units the multiplication quantity, and we modify the double computed property to multiply, which multiplies the counter by the given quantity.

Within the mum or dad, we add the by knowledge property and bind it to the by prop. So now, if we set the by knowledge property to three and the counter is 4, the end result will probably be 4 x 3 = 12.

See the Pen
Vue 3 Slots: Reusing Performance – Multiply Counter
by SitePoint (@SitePoint)
on CodePen.

Utilizing Slots in Renderless Parts

The opposite highly effective means to make use of slots is to place them in a renderless part. A renderless part has no template ingredient. It has a render perform that exposes a single scoped slot. Let’s create a renderless model of our multipurpose listing:

const app = Vue.createApp({
  knowledge() {
    return {
      merchandise: [
        {name: 'Tomatoes', quantity: '4'},
        {name: 'Cucumbers', quantity: '2'},
        {name: 'Red onion', quantity: '1'},
      ]
    }
  }
})

app.part('renderless-list', {
  props: ['items', 'name'], 
  render() {
    return this.$slots.default({
      gadgets: this.gadgets,
      title: this.title 
    });
  }
})

app.mount('#app')
<div id="app">
  <renderless-list title="My buying listing:" :gadgets="merchandise">
    <template v-slot:default="{title, gadgets: merchandise}">
      <h3>{{ title }}</h3>
      <ul>
        <li v-for="product in merchandise" :key="product.title">
          {{ product.amount }} {{ product.title }}
        </li>
      </ul>
    </template>
  </renderless-list>
</div>

Right here, we create a renderless-list part which takes title and gadgets props. It additionally exposes a single scoped slot within the render perform.

Then, within the mum or dad we use it in the same means as our multipurpose listing part — besides that, this time, the content material construction is outlined within the mum or dad, which provides us extra flexibility as we’ll see within the subsequent instance.

See the Pen
Vue 3 Slots: Renderless Multipurpose Part – Record
by SitePoint (@SitePoint)
on CodePen.

Be aware: in Vue 3, this.$scopedSlots is eliminated and this.$slots is used as an alternative. Additionally, this.$slots exposes slots as features. See Slot Unification for extra data.

The actual energy of this part is that we’re not restricted how will render the content material. Let’s see how simple is to render the identical content material as a desk:

<div id="app">
  <renderless-list title="My buying listing:" :gadgets="merchandise">
    <template v-slot:default="{title, gadgets: merchandise}">
      <h3>{{ title }}</h3>
      <desk>
        <tr>
          <th>Amount</th>
          <th>Product</th>
        </tr>
        <tr v-for="product in merchandise" :key="product.title">
          <td>{{ product.amount }}</td>
          <td>{{ product.title }}</td>
        </tr>
      </desk>
    </template>
  </renderless-list>
</div>
desk, th, td {
  border: 1px stable black;
  border-collapse: collapse;
  padding: 5px;
}

Right here, we use the identical performance however we outline totally different content material construction.

See the Pen
Vue 3 Slots: Renderless Multipurpose Part – Desk
by SitePoint (@SitePoint)
on CodePen.

Conclusion

So that is all the facility and adaptability of slots. As we’ve seen, they can be utilized in quite a lot of use instances, which permits us to supply extremely reusable and versatile parts. When mixed with props, slots give us all we have to create advanced parts and apps. Slots are simple to make use of however extraordinarily highly effective. In case you haven’t used them earlier than, I feel now’s the proper time to begin doing so. The perfect place to begin or proceed this journey is the official Slots Documentation. Good luck! 😎

Click to comment

Leave a Reply

Your email address will not be published. Required fields are marked *