Subscribe on changes!

Provide/Inject outside setup

avatar
Jul 3rd 2022

What problem does this feature solve?

As of now, the provide and inject methods only work in the context of the component setup, but they could also be really useful outside that context.

For example a case that I'm facing. I have an app where I want to check if the user wants dark mode when the app is opened, either via a setting that you can change in the app or by media queries in case the setting is not configured. So the part that loads and sets the dark mode preference in App.vue looks something like this:

if(darkModeManuallySet()) {  // Check if the user manually set dark mode
    const setting: boolean = getDarkModeSetting();  // Get the saved setting value
    toggleDarkMode(setting);  // Toggle dark mode accordingly 
    provide('dark_mode_set', setting);  // Provide this setting so other pages can set some stuff taking this into account
} else {
    const prefersDark = window.matchMedia("(prefers-color-scheme: dark)");  // Get preference from media query
    toggleDarkMode(prefersDark.matches);
    provide('dark_mode_set', setting);
}

Then in my settings page, I could have a switch that takes as initial value whether the dark mode is loaded or not:

<template>
    <!-- More page elements -->
    <input type="checkbox" id="darkCheck" v-model="darkMode" >
    <!-- ... -->
</template>

<script setup>
const darkMode = ref(inject('dark_mode_set') as boolean);
// More code...
</script>

What does the proposed API look like?

There isn't any change in API needed in theory, just enabling the API to work outside the setup hook.

avatar
Jul 3rd 2022

"Global provide/inject API" could not exists by design.

Provide/Inject works in component's subtree in Vue App. Value is provided in one instance in one place of tree and injected in another instance in any place of subtree.

In simple words, injected value depends on all upper tree and may be various in different places of the component's tree or even in different Vue apps on the page.

const app1 = createApp(App).provide('key', 'foo');
const app2 = createApp(App).provide('key', 'bar');

globalInject('key'); // ? foo or bar ?

If you need "global provide/inject" you might either not need provide/inject at all or need to provide some global value instead.

So if you need getDarkModeSetting and toggleDarkMode outside Vue component instances then you should create/store them outside components and composables.

There may be dozens ways to do it in JavaScript from simple ES Modules with global reactive variables or global store to IoC container.

For example, a simple solution with global variable without provide/inject:

// 📁 darkMode.js
import { ref, readolny } from 'vue';

export const isDarkMode = ref(false);

export function getDarkModeSetting() {
  return isDarkMode.value;
}

export function toggleDarkModeSettings(newMode) {
  isDarkMode.value = newMode;
}
// 📁 Literally any place of app, inc. setup
import { darkMode } from './path/to/darkMode.js';

const { getDarkMode, toggleDarkMode } = darkMode;

Or you may use approach similar to Vue Router or Vuex plugins. First create thing globally, then provide it.

Create scope with dark mode functionality:

// 📁 createDarkMode.js
import { ref, readolny } from 'vue';

export function createDarkMode(initial) {
  const isDarkMode = ref(initial);

  function getDarkModeSetting() {
    return isDarkMode.value;
  }

  function toggleDarkModeSettings(newMode) {
    isDarkMode.value = newMode;
  }

  return {
    isDarkMode: readonly(isDarkMode),
    getDarkModeSetting,
    toggleDarkModeSettings,
  };
}

Create global dark mode:

// 📁 darkMode.js
import { createDarkMode } from './path/to/createDarkMode.js';

export const darkMode = createDarkMode(false);

Provide global dark mode to Vue app:

// 📁 main.js
import { darkMode } from './path/to/darkMode.js';

const app = createApp(App).provide('DARK_MODE', darkMode);

Inject as usual in components:

// 📁 SomeComponent.vue

const { isDarkMode, toggleDarkMode } = inject('DARK_MODE');

...or use in any other ES module in the app without provide/inject:

// 📁 any place
import { darkMode } from './path/to/darkMode.js';

const { getDarkMode, toggleDarkMode } = darkMode;
avatar
Jul 4th 2022

I don't understand the request here. Your use case doesn't seem to require provide outside a component context.

FYI there's app-level provide: https://vuejs.org/api/application.html#app-provide

avatar
Jul 5th 2022

I think I may have not explained myself properly.

I am using the provide/inject inside a component, but in the onMounted hook, outside the setup scope. In the code that I need this, I check whether the users prefers dark mode with a media query and a class in the body tag, which require to be check when the component is already mounted (which of course comes after setup in another lifecycle).

avatar
Jul 5th 2022

You can use Pinia state management to achieve global provide/inject. It is just easier.More details here

avatar
Jul 5th 2022

I am using the provide/inject inside a component, but in the onMounted hook, outside the setup scope. In the code that I need this, I check whether the users prefers dark mode with a media query and a class in the body tag,

You can still do the injection in setup and then check the result in the hook. No need for any new API.

setup() {
  const prefersDarMode = inject('darkModeCheck')

  onMounted(() => {
    // assuming you provided a ref.
    if (prefersDarkMode.value === true) {
      // do something
    }
  }) 
}
avatar
Jul 5th 2022

My main problem is not with the inject but rather with the provide, as I need to provide the value based on media queries, which I can only access through the document in the onMounted hook.

avatar
Jul 5th 2022
  1. create and provide a ref in setup.
  2. set the value of the ref whenever you are ready to, i.e. in onMounted().
  3. inject and use the ref in children in a way that makes sense, depending on the use case. if its value is set delayed, maybe watch it. or access it in the child's onMounted() ...
avatar
Jul 6th 2022

I think you mistook "provide" as a method to provide a default value rather than to create a store for later use. As above replies said, you should provide a reactive variable (either a ref or a reactive), then you can change the actual value/object property later at anytime. I suggest that you can read official guide about "provide/inject" for various examples.

avatar
Jan 24th 2023

I definitely mistook the wording "provide", "inject" to mean exactly that, set and read values. I was using pinia because of this misunderstanding.

avatar
Feb 9th 2023

I'm creating a Vue plugin. It provide a component, the component has some sane default props value. I want to let the use override the props value in the app.use(component, options).

If I do this:

const options = inject<PluginOptions>(PLUGIN_KEY);

const props = withDefaults(defineProps<Props>(), {
    expanded: false,
    duration: options?.duration || 300,
    hwAcceleration: options?.hwAcceleration || false,
});

I get this warning:

[@vue/compiler-sfc] defineProps() in