Provide/Inject outside setup
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.
"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;
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
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).
You can use Pinia state management to achieve global provide/inject. It is just easier.More details here
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
}
})
}
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.
- create and provide a ref in
setup
. - set the value of the ref whenever you are ready to, i.e. in onMounted().
- 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() ...
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.
I definitely mistook the wording "provide", "inject" to mean exactly that, set and read values. I was using pinia because of this misunderstanding.
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