vue.jsvuejs3

Is there a way to declare provide/inject in a Vue3 dynamic component, outside of the host setup function?


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.


Solution

  • 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:

    1. You call 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.

    1. Send the items to provide into the component as a prop, and let the dynamic component call provide on them.
    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.

    Solution 1

    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>
    

    Solution 2

    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>