fbpx

Designing Vue3 Plugins Using Provide and Inject

Vue dependency injection using provide and inject is great for building Vue3 plugins or avoiding prop drilling.

Although it isn’t used that often, you can implement dependency injection using some just two built-in methods: provide and inject. 

Looking at the Composition API docs, dependency injection using provide and inject will be a lot more common in Vue 3.0. This is primarily because plugins will have to switch to using this pattern because of the Composition API’s change of the this reference.

In this article, we’ll be looking at using provide and inject in Vue3 and how it will be used to build VueJS plugins.

Let’s jump right in!

Why do Vue3 plugins work differently?

In Vue2, most plugins inject properties onto this. For example, Vue Router is accessed via this.$router

However, the setup() method no longer contains the same reference to this. A major reason for this change is the addition of Typescript support.

Sooo, “How are we going to access our plugins now?”

Luckily, we can use provide and inject to help inject dependencies across our Vue application. 

Provide/inject are used for dependency injection – enabling us to provide a plugin in the root of our Vue app and then inject it in a child component. 

In the Composition API, both methods can only be called during the setup() method.

What are provide and inject?

Okay – so we know that we have to use provide and inject, but how does that even work?

Basically, all we need is some sort of key to identify our dependency – for our purposes we’ll be using a Javascript Symbol.

Then, our provide method will associate our Symbol with a certain value and our inject method will retrieve that value using the same Symbol. 

It makes a lot more sense to look at an example.

import { provide, inject } from 'vue'

const LoggedInSymbol = Symbol()

const ParentComponent = {
  setup() {
    provide(LoggedInSymbol, true)
  }
}

const DeepDescendent = {
  setup() {
    // second optional param is a default value if it doesn't exist
    const isLoggedIn = inject(LoggedInSymbol, false)
    return {
      isLoggedIn
    }
  }
}

With this pattern, there are actually a few cool tricks we can accomplish with Vue3

We can provide a dependency globally in our app

If we want to provide something globally, we can use app.provide wherever we declare our Vue app instance. Then, we can just inject the same way we just did.

import { createApp } from 'vue'
import App from './App.vue'


const app = createApp(App)

const ThemeSymbol = Symbol()
app.provide(ThemeSymbol, 'dark')


app.mount('#app')

We can use ref to provide reactive data

This is also extremely handy if we want reactive data to be passed to children components. All we have to do is pass our provide method a reactive property using ref().

// in provider (parent)
const LoggedInSymbol = Symbol()
const loggedIn = ref('true')
provide(LoggedInSymbol, loggedIn)

// in consumer (descendant)
const theme = inject(LoggedInSymbol, ref('false'))

How do we use provide and inject for plugins?

Designing a plugin is actually pretty similar to the simple provide and inject example we just saw. 

However, instead of providing a singular value, we want to use a Composition Function. This is one of the huge benefits of Vue3 – being able to organize and extract code according to functionality.

Since our code should be written in an organized composition function anyways, we just have to create these provide and inject methods and BAM – we have a plugin.

Let’s take a quick look at the hypothetical plugin that the Vue3 Composition API docs gives.

const StoreSymbol = Symbol()

export function provideStore(store) {
  provide(StoreSymbol, store)
}

export function useStore() {
  const store = inject(StoreSymbol)
  if (!store) {
    // throw error, no store provided
  }
  return store
}

Then, our actual components would use it like this.

// provide store at component root
//
const App = {
  setup() {
    provideStore(store)
  }
}

const Child = {
  setup() {
    const store = useStore()
    // use the store
  }
}

As you can see, in some root component, we provide our plugin and pass it a composition function. Then, wherever we would want to use it, we would have to inject it into our component. 

The components should never have to actually make the provide/inject calls and instead should just call the provideStore/useStore methods that the plugin exposes.

So can I still use my old plugins?

Short answer? Yes.

Long answer? Depends on what you mean.

Since the Composition API is purely additive, you could continue using the Options API and have the same reference to this has before and all the old plugins will work the same. 

However, moving forward, it’s definitely worth it to make the leap over to Vue3 and take advantage of all of its features.

Essentially, as long as you want to stick to the Vue2 Options API, your plugins will work the same. But, most well maintained plugins/libraries, should be adding support for Vue3 anyways.

Conclusion

The proper usage of provide/inject is definitely a more advanced topic in Vue development. 

While most typical apps won’t use these concepts, if you’re serious about developing plugins, the changes in the Vue3 Composition API means that you have to use provide/inject. 

If you want more information, definitely check out the Composition API docs

Happy coding!