Subscribe on changes!

Support provide/inject between Vue's Custom Elements (defineCustomElement) and Vue Components (defineComponent)

avatar
Aug 30th 2021

What problem does this feature solve?

As stated in the docs (https://v3.vuejs.org/guide/web-components.html#provide-inject), the Provide/Inject API only works between Vue's Custom Elements.

It makes it harder to use injected libraries inside custom elements.

The use case is :

  • A classic MVC application (Django/Rails-based) which serves data
  • Multiple (async) web components where user interaction is required
  • Shared libraries (i18n, toasts, etc..) for Vue Components (defineCustomElement and defineComponent)
  • Shared internal components, not exposed as web components

What does the proposed API look like?

I've thought of several approaches to implement this.

1. Deep provide function

The provide function (https://github.com/vuejs/vue-next/blob/db1dc1c63097ed62a3f683a7a11c7e819d90bb73/packages/runtime-core/src/apiInject.ts#L58-L60) could search inside parent elements :

let inst = getCurrentInstance().parent
while (inst !== null) {
  if (key in inst.provides) {
    return inst.provides[key]
  }
  inst = inst.parent
}

However, this can be a performance issue when heavily using provide/inject.

It could be enabled when needed as a function parameter like this :

inject('some-instance', { deep: true })

but many libraries use provide inside useLibrary()-like functions. So, library developers would have to add the deep parameter too.

To workaround this, this behavior may be manually enabled with a global configuration when required :

Vue.deepInject = true

2. Implement an event based provide/inject API

The provide/inject API could be reworked with CustomEvents.

There's a current discussion about implement it as a standard : https://github.com/webcomponents/community-protocols/issues/2 so most questions are covered.

There may be other possibilities I did not think about.

Thanks for your work !

avatar
Sep 17th 2021

To clarify, what is the case that is currently not working? Can you provide a code sample demonstrating a simplified case of what you intend to do?

avatar
Sep 17th 2021

Thanks for looking into this! Here are multiple use cases I encountered :

Use case: async component

AppContext.vue

<template>
  <slot />
</template>

<script setup>
// it could be a library like vue-i18n
provide('something', 'test')
</script>

MyComponent.vue

<script setup>
// inject fails. it should not.
const val = inject('something')
</script>

main.ts

customElements.define(
  'my-app-context',
  defineCustomElement(AppContext)
)
customElements.define(
  'my-component',
  defineCustomElement(defineAsyncComponent(() => import('MyComponent.vue')))
)

index.html

<html>
  <body>
    <my-app-context>
      <my-component></my-component>
    </my-app-context>
    <script src="main.ts"></script>
  </body>
</html>

Use case: shared components

AppContext.vue

<template>
  <slot />
</template>

<script setup>
// it could be a library like vue-i18n
provide('something', 'test')
</script>

UtilComponent.vue

<script setup>
// inject fails. it should not.
const val = inject('something')
</script>

MyComponent.vue

<template>
  <UtilComponent />
</template>

main.ts

customElements.define(
  'my-app-context',
  defineCustomElement(AppContext)
)
customElements.define(
  'my-component',
  defineCustomElement(defineAsyncComponent(() => import('MyComponent.vue')))
)

index.html

<html>
  <body>
    <my-app-context>
      <my-component></my-component>
    </my-app-context>
    <script src="main.ts"></script>
  </body>
</html>

I made a reproduction of both usecases here : https://github.com/gnuletik/vue-inject-deep

Thanks!

avatar
Nov 1st 2022

Still doesn't work in Vue 3 it seems?

avatar
Nov 1st 2022

Yes. This has no real prioity for us - the real-life usecase is still cloudy.

Personal Take: Your web components should not depend on a Vue-specific mechanism like provide/inject. Why turn your Vue Components into Web Components if you can only use them in other Vue apps because they depend on provide/inject?

avatar
Nov 2nd 2022

Why turn your Vue Components into Web Components if you can only use them in other Vue apps because they depend on provide/inject?

My usecase was to integrate Vue into a MVC framework like Django, which already provides its own template system. The web components allows you to load vue components and inject variables with <my-custom-com var="{{ django_var }}"></my-custom-comp>.

Your web components should not depend on a Vue-specific mechanism like provide/inject

Using an external mechanism (implementing Vue provide/inject into the app) would work if you don't want to use the existing Vue ecosystem because you can't call the useXX() function of libraries, which usually calls inject.

avatar
Nov 9th 2022

I think the use cases described by @gnuletik are very realistic.

EDIT, that actually works ⬇️

The offical vue documentation is a bit confusing in my opinon:

Vue-defined custom element won't be able to inject properties provided by a non-custom-element Vue component.

This means the parent provider component is a regular vue component and custom element is a child component which cannot inject the provided value.

But it doesn't mention that it is also not possible to provide a value from a custom element down into a sub component of the custom element which is also a regular vue component but it lives in the same project as the parent custom element.

You can get provide/inject to work if you use all child components of a custom element also as custom elements. That means you never use the components: { MySubcomponent } option, all sub components are global.

EDIT end ⬆️

Unfortunately there is still no solution to use provide/inject between custom elements if they were lazy loaded.

avatar
Nov 11th 2022

But it doesn't mention that it is also not possible to provide a value from a custom element down into a sub component of the custom element which is also a regular vue component but it lives in the same project as the parent custom element.

That seems to work fine: Playground

avatar
Nov 14th 2022

That seems to work fine: Playground

You're right! Sorry for the wrong accusations. 💐