I am creating a dynamic component
in Vue3
. I provide the props
using v-bind
.
<component :is='MyComponent' v-bind='myProps' />
I would like to use the provide/inject
feature. How can I get my provided properties into the dynamic component. My dynamic component calls inject
in the setup
function and expects a value to be provided
for its child components.
Although this is not documented on Vue, I have unsuccessfully tried:
<component :is='MyComponent' v-bind='myProps' :provide='myProvidedProps'/>
And even tried putting the provide
object inside the props
object.
After going through the Vue3 source code, there is no way to indicate the provide
spec to a dynamic component
directly in the template. It must be called in the setup function or option of the parent hosting the dynamic component, or in the dynamic component's setup or options.
The 2 options are:
provide
on the component that hosts the dynamic component.setup() {
provide('message', 'hello')
}
<template>
<component :is='myComponent' />
</template>
This does not work for me because my setup function has been called way before my dynamic component has been activated; I also need to set the component type and the provided value together.
function setComponent(someImportedComponent, providedValues) {
myComponent.value = someImportedComponent
myProps.value = {
toProvide: providedValues
}
}
<template>
<component :is='myComponent' v-bind='myProps' />
</template>
MyComponent
setup() {
for(let [key,value] of Object.entries(props.toProvide) ) {
provide(key, value)
}
}
Now this has its issues, because every dynamic component now needs to be responsible for being aware of and calling the passed in provide items.
A way around each component needing to know about the provided values is to create an intermediate component that provides the values.
Providable (Intermediate Component)
<script setup lang="ts">
import {provide} from 'vue'
const props = defineProps<{
is: any
provide?: Record<string, any>
[key: string]: any
}>()
if (props.provide) {
for (const [key, value] of Object.entries(props.provide)) {
provide(key, value)
}
}
const _props = Object.fromEntries(Object.entries(props).filter(it => {
return it[0] !== 'is' && it[0] !== 'provide'
}))
</script>
<template>
<component :is="is" v-bind="_props"/>
</template>
Use it like this:
<template>
<providable :is="myComponent" :provide='toProvide' v-bind='myProps' />
</template>
An even cleaner solution is to create a wrapper component, similar to how keep-alive
works. The target component is simply put into the default slot
.
Provide.vue
<script setup lang="ts">
import {provide} from 'vue'
const props = defineProps<{
value: Record<string, any>
}>()
for (const [key, value] of Object.entries(props.value)) {
provide(key, value)
}
</script>
<template>
<slot name="default"/>
</template>
And use it like this:
<template>
<provide value='toProvide'>
<my-component v-bind='myProps' />
</provide>
</template>