How to render a list of static content with Vue named slot?
Asked Answered
K

3

10

I have trouble figuring out how to get the following to work:

My parent template

<comp>
  <a href="#" slot="links">link 1</a>
  <a href="#" slot="links">link 2</a>
</comp>

and my component comp template looks like the following:

<ul class="comp">
  <li class="comp-item"><slot name="links"></slot></li>
</ul>

currently all my anchors goes to that single li tag (which is expected) but I would like to be able to produce multiple li for every named slot I inserted like the following:

<ul class="comp">
  <li class="comp-item"><a href="#" slot="links">link 1</a></li>
  <li class="comp-item"><a href="#" slot="links">link 2</a></li>
</ul>

Is there any way to achieve what I need without using scoped slot? Because my content is pure HTML so I feel it is unnecessary to put static content inside prop in order to render them.

From what I have seen, most vue UI framework requires you to use another custom component for the list item, which I feel is over killed for the problem. Is there any other way to do this?

Kittykitwe answered 25/2, 2018 at 15:26 Comment(0)
S
15

This is easily accomplished with a render function.

Vue.component("comp", {
  render(h){
    let links = this.$slots.links.map(l => h('li', {class: "comp-item"}, [l]))
    return h('ul', {class: 'comp'}, links)
  }
})

Here is a working example.

console.clear()

Vue.component("comp", {
  render(h){
    let links = this.$slots.links.map(l => h('li', {class: "comp-item"}, [l]))
    return h('ul', {class: 'comp'}, links)
  }
})

new Vue({
  el: "#app"
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="app">
  <comp>
    <a href="#" slot="links">link 1</a>
    <a href="#" slot="links">link 2</a>
  </comp>
</div>

Or with the help of a small utility component for rendering vNodes you could do it like this with a template.

Vue.component("vnode", {
  functional: true,
  render(h, context){
    return context.props.node
  }
})

Vue.component("comp", {
  template: `
    <ul class="comp">
      <li class="comp-item" v-for="link in $slots.links"><vnode :node="link" /></li>
    </ul>
  `
})

new Vue({
  el: "#app"
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="app">
  <comp>
    <a href="#" slot="links">link 1</a>
    <a href="#" slot="links">link 2</a>
  </comp>
</div>
Stinkpot answered 25/2, 2018 at 16:42 Comment(4)
thank you! the vnode solution looks very interesting and just be what I need, I didnt know about this technique!Kittykitwe
You can also use scoped slots right?.. what would be the advantage of render function over scoped slot for this particular use case :-)Rounding
@VamsiKrishna The premise of the question is that the provided links are not in data, but "static" (hardcoded in the slot). So, no, I don't think a scoped slot does anything for you in this case.Stinkpot
@Stinkpot ok got it you are right....but still updated my answer to use a static option instead of placing the links in data optionRounding
R
1

You can make use of scoped slots instead of slots

Your comp component receives a prop links which is an array of links(since static initialized as a custom option). Iterate over the links and pass link as data to the slot just like passing props to a component

   Vue.component("comp", {
  template: `
    <ul class="comp">
      <li class="comp-item" v-for="link in links">
          <slot v-bind="link"></slot>
      </li>
    </ul>
  `,
  props: ["links"]
})

new Vue({
  el: "#app",
  // custom static option , accessed using vm.$options.links
  links: [
      {text: "link1"},
      {text: "link2"},
      {text: "lin3"}
  ]
})

In the parent where the comp component is used make use of a <template> element with a special attribute slot-scope , indicating that it is a template for a scoped slot.

The value of slot-scope will be used as the name of a temporary variable that holds the props object passed from the child:

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="app">
  <comp :links="$options.links">
      <template slot-scope="link">
          <a href="#">{{link.text}}</a>
      </template>
  </comp>
</div>

Here is the working fiddle

Rounding answered 19/3, 2018 at 9:0 Comment(2)
I found the two links outside of slot scope weren't getting rendered in the demoKittykitwe
@Kittykitwe oh...thery were added there by mistake.... as i commented to bert's answer, his answer would be more dynamic one. To learn more why you can check out: adamwathan.me/renderless-components-in-vuejs/…Rounding
B
0

If you don't like to put your data in array, and render list with v-for

You can put all of them in the component, no slot:

<ul class="comp">
  <li class="comp-item"><a href="#">link 1</a></li>
  <li class="comp-item"><a href="#">link 2</a></li>
</ul>

or with slot:

<comp>
  <ul class="comp" slot="links">
    <li class="comp-item"><a href="#">link 1</a></li>
    <li class="comp-item"><a href="#">link 2</a></li>
  </ul>
</comp>
Bute answered 25/2, 2018 at 16:3 Comment(1)
For the first solution, it wont suit my case as I may have different list item in different situations. For the second solution, my slotted HTML will need to know what the required styling classes for the component are, which isn't very modular in my opinion.Kittykitwe

© 2022 - 2024 — McMap. All rights reserved.