Actually slots within a dynamic component element do work. I have have been attempting to solve this same issue, and found this lovely little example by Patrick O'Dacre on CodePen. Patrick has made ample and useful comments in his code which I paste verbatim here for posterity. I omitted the css which you can find on CodePen.
const NoData = {
template: `<div>
This component ignores the data completely.
<p>But there are slots!</p>
<slot></slot>
<slot name="namedSlot"></slot>
</div>`
// In this component, I just ignore the props completely
}
const DefaultMessage = {
template: `<div>
This component will show the default msg: <div>{{parentData.msg}}</div>
</div>`,
// this component won't have posts like the Async Component, so we just ignore it
props: ['parentData']
}
const CustomMessage = {
template: `<div>
This component shows a custom msg: <div>{{parentData.msg}}</div>
</div>`,
// this component won't have posts like the Async Component, so we just ignore it
props: ['parentData']
}
const Async = {
template: `<div>
<h2>Posts</h2>
<p>{{parentData.msg}}</p>
<section v-if="parentData.posts.length > 0">
<ul>
<li class="postInfo" v-for="post in parentData.posts">
<div class="postInfo__title">
<strong>Title:</strong> {{post.title}}
</div>
</li>
</ul>
</section>
</div>`,
props: ['parentData']
}
/* Children should only affect parent properties via an EVENT (this.$emit) */
const ChangeMessage = {
template: `<div>
<p>Type here to change the message from the child component via an event.</p>
<div><input type="text" v-model="message" @input="updateDateParentMessage" /></div>
</div>`,
data() {
return {
// initialize our message with the prop from the parent.
message: this.parentData.msg ? this.parentData.msg : ''
}
},
props: ['parentData'],
/* Need to watch parentData.msg if we want to continue
to update this.message when the parent updates the msg */
watch: {
'parentData.msg': function (msg) {
this.message = msg
}
},
methods: {
updateDateParentMessage() {
this.$emit('messageChanged', this.message)
}
}
};
const Home = {
template: `<section>
<div class="wrap">
<div class="right">
<p><strong>Change the current component's message from the Home (parent) component:</strong></p>
<div><input type="text" v-model="dataForChild.msg" /></div>
<p><strong>Important!</strong> We do not change these props from the child components. You must use events for this.</p>
</div>
</div>
<div class="controls">
<button @click="activateComponent('NoData')">No Data</button>
<button @click="activateComponent('DefaultMessage')">DefaultMessage</button>
<button @click="activateComponent('CustomMessage', {posts: [], msg: 'This is component two'})">CustomMessage</button>
<button @click="getPosts">Async First</button>
<button @click="activateComponent('ChangeMessage', {msg: 'This message will be changed'})">Change Msg from Child</button>
<button @click="deactivateComponent">Clear</button>
</div>
<div class="wrap">
<div class="right">
<h2>Current Component - {{currentComponent ? currentComponent : 'None'}}</h2>
<!-- ATTN: Uncomment the keep-alive component to see what happens
when you change the message in ChangeMessage component and toggle
back and forth from another component. -->
<!-- <keep-alive> -->
<component
:is="currentComponent"
:parentData="dataForChild"
v-on:messageChanged="updateMessage">
<div class="slotData">This is a default slot</div>
<div slot="namedSlot" class="namedSlot">This is a NAMED slot</div>
<div slot="namedSlot" class="namedSlot"><p>Here we pass in the message via a slot rather than as a prop:</p>{{dataForChild.msg}}</div>
</component>
<!-- </keep-alive> -->
</div>
</div>
</section>`,
data() {
return {
currentComponent: false,
/* You don't NEED to put msg and posts here, but
I prefer it. It helps me keep track of what info
my dynamic components need. */
dataForChild: {
// All components:
msg: '',
// Async Component only
posts: []
}
}
},
methods: {
/**
* Set the current component and the data it requires
*
* @param {string} component The name of the component
* @param {object} data The data object that will be passed to the child component
*/
activateComponent(component, data = { posts: [], msg: 'This is a default msg.'}) {
this.dataForChild = data;
this.currentComponent = component;
},
deactivateComponent() {
this.dataForChild.msg = '';
this.currentComponent = false;
},
/* Hold off on loading the component until some async data is retrieved */
getPosts() {
axios.get('https://codepen.io/patrickodacre/pen/WOEXOX.js').then( resp => {
const posts = resp.data.slice(0, 10) // get first 10 posts only.
// activate the component ONLY when we have our results
this.activateComponent('Async', {posts, msg: `Here are your posts.`})
})
},
/**
* Update the message from the child
*
* @listens event:messageChanged
* @param {string} newMessage The new message from the child component
*/
updateMessage(newMessage) {
this.dataForChild.msg = newMessage
}
},
// must wire up your child components here
components: {
NoData,
CustomMessage,
DefaultMessage,
Async,
ChangeMessage
}
}
const routes = [
{ path: '/', name: 'home', component: Home}
];
const router = new VueRouter({
routes
});
const app = new Vue({
router
}).$mount("#app")
The html,
<div id="app">
<h1>Vue.js Dynamic Components with Props, Events, Slots and Keep Alive</h1>
<p>Each button loads a different component, dynamically.</p>
<p>In the Home component, you may uncomment the 'keep-alive' component to see how things change with the 'ChangeMessage' component.</p>
<nav class="mainNav"></nav>
<!-- route outlet -->
<!-- component matched by the route will render here -->
<section class="mainBody">
<router-view></router-view>
</section>
</div>
{main: <some html>, meta: <some html>}combined withv-html. There's no simple way I can conceive of to specify a slot in which to render in a child component. Maybe someone else will have an idea how to do that. - Bert