Home > Blockchain >  Vue 2 set data or prop or anything on child slot
Vue 2 set data or prop or anything on child slot

Time:10-07

I have following components:

Child.vue

<template>
  <li>
      {{ idx }}
  </li>
</template>
<script lang="ts">
import Vue, { defineComponent } from 'vue';

export default defineComponent({
  data () {
    return {
      idx: 0
    }
  }
});
</script>

Parent.vue

<template>
  <nav>
    <slot />
  </nav>
</template>
<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
  mounted() {
    //
    const slots = (this.$scopedSlots.default ? this.$scopedSlots.default(this.$attrs) : []);
    for (let i = 0; i < slots!.length; i  ) {
      const s = slots![i];
      // Essential magic must happen here, but it does not :(
      if (s.data?.attrs) slots![i].data!.attrs!.idx = i;
    }
  },
});
</script>

Controls.vue

<template>
  <Parent>
   <Child />
   <Child />
   <Child />
  </Parent>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import Parent from './Parent.vue';
import Child from './Child.vue';

export default defineComponent({
  components: { Parent, Child }
});
</script>

So as the outcome I wish every Child to know its position or index, but what I get is 000, so setting data on child component doesn't work for some reason. Any hint on improving typescript code are also welcome.

Here is codepen in js: https://codesandbox.io/s/codepen-vue-forked-focjc1

CodePudding user response:

You're trying to break the governing principle of slots.
They were created as a mechanism to transfer template responsibility from the slot bearer component (in our example: <Parent />) to the slot content provider component (in our example: <Controls />).

More precisely, the main purpose of <slot>Default HTML</slot> (inside <Parent />) is to either render Default HTML when used as

<Parent />

, in which case Default HTML's parent is <Parent />, or to render Replacement HTML when used as

<Parent>Replacement HTML</Parent>

, in which case Replacement HTML's parent is the slot content provider (<Controls />).

We can't tell Parent.vue: "you know what, even if you're not in charge of providing the slot contents anymore, I still want you to meddle with it".
I mean, we can try, but slots were not developed for this purpose and there's no public API to hook into the rendering process of slots. Even if we figure out a way to do it today, we have no guarantee it won't break in the future, without warning.

If we find ourself needing anything from <Parent /> while crafting the slot contents (in <Controls />) we can pass it from Parent to Controls using slot scope:

<!-- Parent.vue -->
<template>
  <nav>
    <slot :foo="foo" />
  </nav>
</template>

<!-- Controls.vue -->
<Parent>
  <template #default="{ foo }">
    // use Parent's `foo` inside Controls
  </template>
</Parent>

In our specific case, let's declare idx prop on <Child /> and populate it from <Controls />

<!-- Controls.vue -->
<template>
  <Parent>
    <Child v-for="n in 3" :key="n" :idx="n - 1" />
  </Parent>
</template>

Working demo.

  • Related