JAVASCRIPT

Vue 3 Dependency Injection with `provide` and `inject`

Learn to use Vue 3's `provide` and `inject` functions to pass data and functions down a component tree without prop drilling, simplifying state management for deeply nested components.

// src/keys.js (Optional: For type-safe keys in TypeScript or consistency)
import { Symbol } from 'vue';

export const USER_INFO_KEY = Symbol('userInfo');
export const APP_SETTINGS_KEY = Symbol('appSettings');
export const NOTIFICATION_SERVICE_KEY = Symbol('notificationService');

// src/components/AppProvider.vue (Top-level component or wrapper)
<template>
  <div class="app-provider">
    <slot></slot>
  </div>
</template>

<script setup>
import { provide, ref, readonly } from 'vue';
import { USER_INFO_KEY, APP_SETTINGS_KEY, NOTIFICATION_SERVICE_KEY } from '../keys';

// Example user data
const currentUser = ref({
  id: 1,
  name: 'Alice Smith',
  email: '[email protected]',
  role: 'admin'
});

// Example settings
const settings = ref({
  theme: 'dark',
  language: 'en-US',
  notificationsEnabled: true
});

// Example service (could be a complex object with methods)
const notificationService = {
  show: (message, type = 'info') => {
    console.log(`Notification (${type}): ${message}`);
    // In a real app, this would trigger a global notification system
  },
  hide: () => {
    console.log('Hide notification');
  }
};

// Provide the data and service
provide(USER_INFO_KEY, readonly(currentUser)); // Use readonly to prevent direct mutation
provide(APP_SETTINGS_KEY, settings); // Mutable if needed
provide(NOTIFICATION_SERVICE_KEY, notificationService);

// Optional: Function to update user data
const updateUserName = (newName) => {
  currentUser.value.name = newName;
};
provide('updateUserName', updateUserName); // Can provide functions too
</script>

// src/components/DeeplyNestedComponent.vue
<template>
  <div class="nested-component">
    <h3>Deeply Nested Component</h3>
    <p>User: {{ userInfo?.name }} (Role: {{ userInfo?.role }})</p>
    <p>Theme: {{ appSettings?.theme }}</p>
    <button @click="showInfo">Show Notification</button>
    <button @click="updateUser">Update User Name</button>
    <GrandChildComponent />
  </div>
</template>

<script setup>
import { inject } from 'vue';
import { USER_INFO_KEY, APP_SETTINGS_KEY, NOTIFICATION_SERVICE_KEY } from '../keys';
import GrandChildComponent from './GrandChildComponent.vue';

const userInfo = inject(USER_INFO_KEY);
const appSettings = inject(APP_SETTINGS_KEY);
const notificationService = inject(NOTIFICATION_SERVICE_KEY);
const updateUserName = inject('updateUserName'); // Injecting a provided function

const showInfo = () => {
  notificationService.show(`Hello, ${userInfo.value.name}!`, 'success');
};

const updateUser = () => {
  if (updateUserName) {
    updateUserName('Bob Johnson');
    console.log('User name updated via injected function.');
  }
};
</script>

// src/components/GrandChildComponent.vue
<template>
  <div class="grandchild-component">
    <h4>Grandchild Component</h4>
    <p>Current Language: {{ appSettings?.language }}</p>
  </div>
</template>

<script setup>
import { inject } from 'vue';
import { APP_SETTINGS_KEY } from '../keys';

const appSettings = inject(APP_SETTINGS_KEY, { language: 'default' }); // With default value
</script>

// src/App.vue (Root usage)
/*
<template>
  <AppProvider>
    <h1>My Vue App</h1>
    <DeeplyNestedComponent />
  </AppProvider>
</template>

<script setup>
import AppProvider from './components/AppProvider.vue';
import DeeplyNestedComponent from './components/DeeplyNestedComponent.vue';
</script>
*/
How it works: This snippet demonstrates how Vue 3's `provide` and `inject` functions enable dependency injection. A `AppProvider` component uses `provide` to make `currentUser` (readonly), `settings`, and a `notificationService` available to all its descendants, no matter how deeply nested. Components like `DeeplyNestedComponent` and `GrandChildComponent` then use `inject` to consume these provided values without having to pass them down through props at each level. Using `Symbol`s as keys helps avoid naming collisions and provides better type safety, especially in TypeScript.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs