Sometimes in Vuejs applications, it’s required to pass data to deeper components in but this leads to complexity in the application design, let’s see in this tutorial the Provide Inject syntax to solve such problems.
Sending data from parent component to a child component is a common aspect in frontend frameworks like Vuejs. Most of the time we are passing data in the form of “props” that is consumed by child components to do specific functionality.
However there is a problem usually arise here in some scenarios, when you have several levels of nested components and you need to pass a prop(s) to a deepest component in the tree. Thereby the required prop will have to be passed to every component in the parent chain until you reach the target component. This kind of problem called “props drilling”.
“props drilling” introduces some series problems, because the middle components might be not care about these props at all. Second if you need to change, alter or add new props you have to do this in all the components chain.
Some solutions to the “props drilling” issues that developers usually use is to implement a store “Vuex” or “Pinia”. However creating the store is not a simple task and might contain complex logic.
Instead in Vuejs there are a syntax called “Provide/Inject”, in other words we can supply or provide the props that need to be consumed by a child in component in the root component. And in the other side on the child component we inject or receive these props using the “inject keyword”. So the parent is a provider and the child is an injector.
This syntax is somewhat similar to Reactjs Context Api.
Example App
We have a simple application that contains three components:
- <App/> root component
- <AppHeader />
- <ProfileMenu />
The code for each component as follows:
app.vue
<template> <AppHeader /> </template> <script> import AppHeader from './components/AppHeader.vue' export default { name: 'App', components: { AppHeader } } </script>
AppHeader.vue
<template> <header> <nav> <ul> <li><a href="#">Home</a></li> <li><a href="#">About</a></li> <li><a href="#">Contact</a></li> </ul> </nav> <ProfileMenu class="profile-menu" /> </header> </template> <script> import ProfileMenu from './ProfileMenu'; export default { name: 'AppHeader', components: { ProfileMenu } } </script> <style> nav { display: inline-block; } nav ul { list-style-type: none; } nav ul li { display: inline; margin: 5px; } nav ul a { text-decoration: none; font-family: Arial; color: #b34545; font-weight: bold; font-size: 20px; } .profile-menu { display: inline; margin-inline: 66px; } .profile-menu span { color: green; } </style>
ProfileMenu.vue
<template> <div> Hello <span>Foe Bar</span> </div> </template> <script> export default { name: 'ProfileMenu' } </script>
Using Provide
Add the “provide” option to the component the same way we use “props” option:
App.vue
<template> <AppHeader /> </template> <script> import AppHeader from './components/AppHeader.vue' export default { name: 'App', components: { AppHeader }, provide: { username: 'John Doe' } } </script>
As you see i just added just one key to the provide option to be received in the child components.
If you want to receive this option from state for example something declared in the the data(), then replace the provide object to be a function that returns object like so:
export default { name: 'App', components: { AppHeader }, data() { return { username: 'John Doe' } }, provide() { return { username: this.username } } }
Using Inject
To receive the data provided by a parent component as in the above component add the inject option:
Let’s display the username in the <ProfileMenu /> component:
ProfileMenu.vue
<template> <div> Hello <span>{{username}}</span> </div> </template> <script> export default { name: 'ProfileMenu', inject: ['username'] } </script>
Here we are specifying the key of the provided data passed in the provide option in the ancestor component in this case “username”.
You can alias the key of the injected data to use a different name using the object syntax instead of the array syntax like this:
<template> <div> Hello <span>{{my_name}}</span> </div> </template> <script> export default { name: 'ProfileMenu', inject: { my_name: { from: 'username' } } } </script>
Here the object contains the injected data where we specify a new key as the object key to act as the new name (alias) and then reference it inside the template using this.<alias> like this.my_name above.
Reactivity When Using Provide & Inject
Reactive data don’t work by default when using injected data. To demonstrate this let’s create a simple input where we set the username dynamically.
App.vue
<template> <AppHeader /> <div> <p> <label>Enter username</label> <input type="text" v-model="this.username" /> </p> </div> </template> <script> import { computed } from 'vue'; import AppHeader from './components/AppHeader.vue' export default { name: 'App', components: { AppHeader }, data() { return { username: 'John Doe' } }, provide() { return { username: this.username } } } </script>
Here i am attached the state variable username to the input v-model, so whenever we type or change the input data it should be reflected directly on the injected data. However if you run this it won’t work.
To solved this and here i am talking in the context of Vue 3, we can use the computed() function introduced in Vue 3 composition Api.
Let’s refactor the provide option to use the computed() function:
import { computed } from 'vue'; .... .... export default { .... .... , provide() { return { username: computed(() => this.username) } } }
First i imported the computed() function, then i wrapped the injected data inside computed.
Now if you run the code and type in the input it will be updated instantly in the header.
You many encounter this warning in the console when using computed() alongside provide:
[Vue warn]: injected property “my_name” is a ref and will be auto-unwrapped and no longer needs `.value` in the next minor release. To opt-in to the new behavior now, set `app.config.unwrapInjectedRef = true` (this config is temporary and will not be needed in the future.)
According to Vue 3 docs, this warning tells that you need to set app.config.unwrapInjectedRef=true in main.js to make injections automatically unwrap computed refs like so:
main.js
const app = createApp(App); app.config.unwrapInjectedRef=true;
App-level Provide
Beside proving data inside components you can provide data through the main App instance, so that all components can inject these data and this is useful in scenarios when you need to provide global configuration data.
main.js
import { createApp } from 'vue' import App from './App.vue' const app = createApp(App); app.config.unwrapInjectedRef=true; app.provide('locale', 'en'); app.provide('country', 'usa'); app.provide('currency', 'usd'); app.mount('#app');
SomeComponent.vue
export default { ...... ...... ......, inject: ['locale', 'country', 'currency'], created() { console.log(this.locale, this.country, this.currency); } }
Provide & Inject in Vue 3
In vue 3 composition Api there is two new methods provide and inject that can be used in setup() function the same way as options api.
import { provide } from 'vue' export default { setup() { provide('key', 'value') } }
child component
import { inject } from 'vue' export default { setup() { const value = inject('key') return { value } } }