Phil
Phil

Reputation: 2252

Cannot pass data to slot from owning component and parent context in Vue.JS

In the code below I have parent/child components where some data (I'm just using literals) is passed from the root context (data1) whilst other data is applied to the slot inside the parent component (data2). The output shows that data1 is available but data2 is not.

Why is the data2 literal not passed to the child component? I've google'd and seen references to using template with a scope but I don't understand why that would work when I'm not using v-bind in this example.

I suspect I've misunderstood something but would certainly appreciate some help!

Vue.component(
  'child', {
    props: ['data1', 'data2'],
    template: `
<div>
  <p>The child component - data1: "{{data1}}", data2: "{{data2}}"</p>
</div>`
  }
)

Vue.component(
  'parent', {
    template: `
<div>
  <p>The parent component</p>
  <slot data2="data from parent component"/>
</div>`
  }
)

new Vue({
  el: '#el',
  template: `
<div>
  <parent>
    <child data1="data from root"></child>
  </parent>
</div>`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="el" />

Update 04/JAN

I have an alternative use case where I'm trying to encapsulate Bootstrap syntax within a component. Given this is a more concrete example it may be easier to consider. Here I want all the Bootstrap specifics inside the form-group component but I can't seem to define class="form-control" on the slot instead of the template being passed in.

I have to admit that given what @collapsar has said I think this may be a different problem but I'm still in the process of getting my head around this!

Vue.component('formGroup', {
  props: ['label'],
  template: `
     <div class="form-group">
        <label for="inputEmail3" class="col-sm-2 control-label">{{label}}</label>
        <div class="col-sm-10">
          <!-- I'd like to see this the class defined here merged with the template as described
          in https://v2.vuejs.org/v2/guide/class-and-style.html#With-Components although admittedly
          that's for components and not slots -->
          <slot class="form-control"/>
        </div>
      </div>`
})

new Vue({
  el: '#el',
  template: `
<div class="container">
    <form class="form-horizontal">
      <form-group label="Email">
        <!-- I don't want the class defined on this control but added by the form-group instead -->
        <input type="email" class="form-control" id="inputEmail3" placeholder="Email">
      </form-group>
    </form>
</div>
`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="el">
</div>

Upvotes: 0

Views: 3152

Answers (1)

collapsar
collapsar

Reputation: 17238

You appear to conceptualize the slot mechanism in a slightly wrong way:

  • Data transfer from parent to child happens through properties which are declared in the child and defined in the parent.
  • Parts of the child component's template may be defined in the parent. The child component integrates these parts by means of the slot mechanism.
  • The template snippet may contain data placeholders. These placeholders are resolved by the child component (ie. the user of the snippet), which is where the slot element comes into play: its attributes instantiate the parameters The rationale for why these placeholders exist in the first place might be that they are to be computed by the child component.
  • Finally a syntactic hook in the template snippet is needed as a placeholder mark, since the placeholder will be instantiated by the child component. This is the name of a JS object which will serve as a container for actual parameter values. As these values are specified by the child there is no way to know this container object's structure beforehand without abandoning the (child) component's data encapsulation which would would defeat the very purpose of component use. The 'slot-scope' attribute in the snippet definition contains the name of the said JS object (the necessary declaration takes place automagically in the background).

See it in code:

Vue.component(
  'child', {
    props: ['data1', 'data2'],
    template: `
<div>
  <p>The child component</p>
  <p>
      <span>Passed through parent: {{data1}}</span><br/>
      <span>Passed from parent: {{data2}}</span><br/>
      <slot data3="Data from the child template funnelled into a parent-defined slot"/>
  </p>
</div>`
  }
)

Vue.component(
  'parent', {
    props: ['data1'],
    template: `
<div>
  <p>The parent component</p>
  <child
    :data1="data1"
    :data2="'data from parent'"
  >
    <template slot-scope="mixme">
        <span>{{mixme.data3}}</span><br/>
    </template>
  </child>
</div>`
  }
)

//
//  Vue instance
//  The template references the component 'parent' only.
//  As 'child' is embedded into 'parent', this component will be referenced in _parent_'s template.
//
//  Also note that 'data1' is passed down as a genuine property instead of a DOM attribute.
//
new Vue({
  el: '#el',
  template: `
<div>
  <parent
    :data1 = "'data from root'"
  />
</div>`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="el" />

Upvotes: 2

Related Questions