Reputation: 575
I have a component that is nested inside several layers of components. A scoped slot can be passed down to this component. I want to detect when it is not.
Example:
Vue.component("parent", {
template: `
<child>
<template slot="expand" slot-scope="{data}" v-if="$scopedSlots.expand">
<slot name="expand" :data="data"></slot>
</template>
</child>
`,
});
Vue.component("child", {
template: `
<div>
<slot name="expand" :data="1"></slot>
<div @click="$scopedSlots.expand && log()">Child</div>
</div>
`,
methods: {
log() {
console.log(this.$scopedSlots.expand);
console.log("This shouldn't work");
}
}
});
new Vue({
el: "#app",
template: `<parent></parent>`,
})
In this example, the scoped slot is never passed in from the root component to <parent>
. As a result, I would have thought that $scopedSlots.expand
and the v-if
in <parent>
would both be falsy, so no slot would be passed to <child>
, and clicking wouldn't do anything.
However, this appears to be wrong for 2 reasons:
$scopedSlots.expand
is stored internally as a function, it will always evaluate as truthy, as you can see in the console of the CodePen.<template>
s always render, even with v-if="false"
. So even if $scopedSlots.expand
would evaluate as falsy inside <parent>
, an empty slot would still be passed down to <child>
, and $scopedSlots.expand
inside <child>
would evaluate as truthy regardless.Is there a way to make this work?
I know I can accomplish this by just passing a boolean prop that says whether to process the click. I'm trying to figure out if it's possible to determine from the slot itself.
Upvotes: 4
Views: 3091
Reputation: 34306
I've been hit with this before, it's pretty annoying.
The Vue template compiler will compile this
<template v-if="false"/>
into essentially
vm._e()
where _e
is an internal helper method which is createEmptyVNode
. So off the bat we know to expect empty vnodes instead of falsey values.
If we investigate further by looking at the compiled code for this template
<child>
<template slot="expand" slot-scope="scope" v-if="false"/>
</child>
we get this code (which I've simplified a bit to show only the relevant stuff)
_c('child', {
scopedSlots: {
expand(scope) {
return undefined
}
}
})
To summarize: v-if="false"
does not imply that the scoped slot function will be omitted, instead it will be provided but when called it will return undefined
. So in the child component, you can expect $scopedSlots.expand
to exist but when called will return undefined
.
AFAIK there's no easy way to deal with this. A quick "fix" for your example would be
<div @click="$scopedSlots.expand() && log()">Child</div>
^^
but be wary of
slot-scope="{data}"
for a nonexistent {data}
? It might be best to call $scopedSlots.expand(scope)
with real scope
data.$scopedSlots.expand()
and discarding the potentially truthy result multiple times in a render cycle could be a performance issue.FWIW I've had varied success with separating the v-if
and slot
into separate <template>
nodes:
<template v-if="$scopedSlots.expand">
<template slot="expand" slot-scope="{data}">
<slot name="expand" :data="data"/>
</template>
</template>
Upvotes: 5