when writing VueJS (2.5.22) application using Typescript and trying to add component dynamically at run-time. I ran into two issues with Typescript. Adding child component as named slots and subscribing to emit event from child components. I was able to work around slots by appending child component, however i would like to use named slots. Any input on slot is appreciate.
when still trying to get second issue resolved of parent component subscribing to child event and updating property in parent component.
The child component is going to emit “update-value”. How do i subscribe to this event on parent component dynamically?
Thanks,
Please create tag for: vuejs-component vuejs-dynamic-component vuejs-typescript vuejs-emit
parent component adding dynamically at run-time in created method
<div>
<p>Dynamic components</p>
<div ref=”controls”></div>
</div>
export default class ParentComponent extends vue.js {
public $refs: Vue[‘$refs’] & {
controls: HTMLElement
}
public $slots: Vue[‘$slots’] & {
TextBox: TextBox
}
vuejs created method
-——————-
const labelControlContainerClass = Vue.extend(LabelControlContainer)
const textBoxClass = Vue.extend(TextBox)
const fields = table.fields
for (const key in fields) {
if (fields.hasOwnProperty(key)) {
const element = fields[key]
if (element.fieldType === ‘Char’) {
const textBoxInstance = new textBoxClass({
// props
propsData: {
value: ‘’,
placeholder: element.translatedCaption,
name: key
},
// how to subscript to “update-value” event???
})
textBoxInstance.$mount()
const instance = new labelControlContainerClass({
// props
propsData: {
caption: element.translatedCaption,
bold: false
},
})
instance.$mount()
instance.$el.appendChild(textBoxInstance.$el) // add child component, try adding named slots, but didn’t work
this.$refs.controls.appendChild(instance.$el)
}
}
}
}
label component with slot. named slot didn’t worked
<template>
<div class=”controlContainer” :class=”{vertial: labelTop}”>
<span v-bind:class=”{bold: bold}” class=”.controlContainer__cisLabel”>{caption}</span>
<slot name=’control’></slot>
<slot></slot>
</div>
</template>
<script lang=’ts’>
import vue.js from ‘vue’
import { Component, Prop } from ‘vue-property-decorator’
@Component({
})
export default class LabelControlContainer extends vue.js {
@Prop({ type: String, default: ‘’ }) public caption: string
@Prop({ type: Boolean, default: true }) public bold: boolean
@Prop({ type: Boolean, default: false }) public labelTop: boolean
}
</script>
child component that is going to added to slot and emit on value change
export default class TextBox extends vue.js {
@Prop({ type: String, default: ‘placeholder text’ }) public placeholder: string
@Prop({ type: Object, default: () => ({}) }) public attributes: any
@Prop({ type: Boolean, default: false }) public readonly: boolean
@Prop({ type: String, default: ‘text’ }) public mode: string
@Prop({ type: Boolean, default: true }) public isValid: boolean
@Prop({ type: String, default: ‘’ }) public value: string
@Prop({ type: String, default: ‘’ }) public name: string
@Prop({ type: Boolean, default: false }) public setFocus: boolean
private default = {}
private unlockable: boolean = this.readonly
private valueChanged(data: any): void {
this.$emit(‘update-value’, data.value, this.name)
}
}
Solution :
I’m not using TypeScript but I think this is not about TypeScript so I will answer using plain JavaScript.
To programmatically add slot element you can use $slots property like this:
vm.$slots.default = [‘Hello’]
vm.$slots. foo = [‘Hello’] // for named slots
But unfortunately the slots must be the array of VNodes, not normal DOM Element or vue.js Components. You can use the $createElement method to create them like so:
vm.$slots.default = [vm.$createElement(‘div’, [‘Hello’])]
vm.$slots.default = [vm.$createElement(‘HelloWorld’)] // custom component
So the code will look like that:
const TextBox = {
template: `
<div>{ text }</div>
`,
props: [‘text’]
}
const LabelControl = {
template: `
<div>
<slot name=’control’></slot>
</div>
`,
components: {
TextBox
}
}
const LabelControlComponent = Vue.extend(LabelControl)
new Vue({
el: ‘#app’,
template: `
<div>
<div ref=’controls’></div>
</div>
`,
mounted () {
let texts = [‘a’, ‘b’, ‘c’, ‘d’]
texts.forEach(text => {
let labelControl = new LabelControlComponent()
labelControl.$slots.control = [
labelControl.$createElement(‘TextBox’, {
props: { text }
})
]
labelControl.$mount()
this.$refs.controls.appendChild(labelControl.$el)
})
}
})
Listening to events from a slot child seems impossible, as it’s a different scope, but you can still use the $parent property to call a parent method directly or emit an event from the parent.
const TextBox = {
template: `
<div @click=’$parent.show(text)’>{ text }</div>
`
}
const LabelControl = {
methods: {
show (text) {
this.text = text
}
}
}
JSFiddle Example
References:
How to create Vue.js slot programatically?
vue.js pass slot template to extended component
Is it possible to emit event from component inside slot