-
-
Notifications
You must be signed in to change notification settings - Fork 33.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Provide/inject for custom directives #6487
Comments
Can you please be more explicit about what kind of directive would need that, please? If you mean setting the configuration when using the directives (in the template), well, I think it's much better to explicitly give the directive a configuration that making it depend on an injected value somewhere else, which could lead to hard to debug bugs. On a side note, inject/provide is meant for advanced usage on libs where we want to make things easier to use without having the user to worry about connecting explicitly many things together, but is discouraged in applications because it makes things implicit and harder to understand/debug |
I don't think this is really needed. If you really want to have a global configuration for all your directives I would do this with a vuex store object, this would even make it easy to update when you need to |
@jsnanigans I'm not sure what you mean. How does a plugin have access to the user's Vuex store? And this is not about global configuration. It's about context-aware configuration. |
I was thinking you could define the config I think I misunderstood what you are trying to accomplish with provide/inject for directives.. |
@posva I understand provide/inject is for advanced usage and I'm not trying to change that. All I'm saying is that provide/inject should work the same way for directives as it does already for components. And for the same reasons. Let me give you a more concrete example (apologies I should have done this from the start): I have a plugin that provides the primaryColor (config) {
return `color: ${config.theme === 'fun' ? '#ff0000' : '#000'};`
} <greetings>
<div v-css="primaryColor">Nice</div>
<div>to</div>
<div v-css="primaryColor">meet</div>
<div v-css="fontWeight700">you</div>
</greetings> The Vue.use(cssDirective(config)) Ok, that works globally, but what if we want to use different themes in our app: <greetings>
<div v-css="primaryColor">Nice</div>
<div>to</div>
<div v-css="primaryColor">meet</div>
<div v-css="fontWeight700">you</div>
</greetings>
<features>
<div v-css="primaryColor">Shiny</div>
<div>Affordable</div>
<div v-css="primaryColor">Durable</div>
<div v-css="fontWeight700">Eco-friendly</div>
</features> In my suggestion, the greetings and features components can <greetings>
<div v-css="[config, primaryColor]">Nice</div>
<div>to</div>
<div v-css="[config, primaryColor]">meet</div>
<div v-css="fontWeight700">you</div>
</greetings>
<features>
<div v-css="[otherConfig, primaryColor]">Shiny</div>
<div>Affordable</div>
<div v-css="[otherConfig, primaryColor]">Durable</div>
<div v-css="fontWeight700">Eco-friendly</div>
</features> Please do not get hung up on this somewhat-complex CSS-in-JS example. I can think of other use cases as well, but this is my most immediate use case. |
I really think directives are probably not what you want to use for CSS in js.
I want to reemphasize that It's meant for libs not applications |
would it be so much easier to just do this with <greetings>
<div class="primaryColor">Nice</div>
<div>to</div>
<div :style="primaryColor">meet</div>
<div :style="fontWeight700">you</div>
</greetings> primaryColor: _ => {color: this.config.theme.primaryColor} |
I strongly disagree. This use case fits within the guides description of "low-level DOM access on plain elements" and is a perfect example of why directives should exist in the first place. Why? Because the directive is only changing the class value on a single element. No user wants to litter their app with a bunch of Higher Order Components just to deliver a class name.
Yes, I agree, this is a downside, but you could say the same for provide/inject functionality in components and that is already in Vue. You could also say the same about React's Context.
I'm sorry but I don't really understand this. Provide/inject can be used in components right? Components are only used in applications. |
As you can imagine, it's not because it's dirty that we can throw dump to it
Components can be added by libs |
@posva That is not a fair summation of what I was saying. I'm saying that debating provide/inject's pros and cons is pretty much pointless because it is already a framework feature. It solves the same problem as React's Context: the need for context-aware data delivery. My issue is that components can receive data in this way and directives cannot. Why? You can do just as much harm if not more with components. This feature exists anyway because maintainers ( maybe even yourself :) ) have decided that the pros outweigh the cons. Is there a con that applies to directives that doesn't also apply to components?
Yes and directives can be added by libs too. |
What I'm trying to say is that adding p/i to directives could make more harm than help... I pointed out the link because it talks about the purpose of provide-inject... |
@posva Sorry, that was not my intention at all. I just wanted to clarify for anyone reading this that both directives and components can be installed via libraries and so that is not a difference between the two when considering whether one or both have a legitimate use for P/I. |
But it's not like that, there's a difference. |
Ok, what is the difference you're referring to? Or what is it about what you quoted that is incorrect (I'm legitimately trying to figure out what you mean)? I have another use case involving input validation but it's not much different from the use case I've already laid out. They are both essentially a more convenient way to set configuration so that you don't have to specify it over and over again as long as you're within a certain context. This seems to me like it is the whole point of P/I as a feature but I feel like you are dismissing my use case without giving a reason. Just to be clear, I'm advocating for this feature as a library author. My end goal is to make using the library easier in the event that the library's API is better suited to use directives instead of components. |
I just stumbled onto this as well. I would have liked to write a directive to access a shared object that is injected in components but inaccessible in a directive. The shared object is the main instance of our application which gives us access to some state. In this case whether or not running on a Phone,Tablet,Desktop etc. Based on that information the v-responsive directive would behave slightly differently when setting appropriate classes on the bound element. I have to duplicate some detection code out now and still won't be able to allow a consumer of our application instance to override within that instance (there can be more than one on screen in some scenarios). Overriding is also required for SSR btw. |
vnode.context.$root will give you the main instance as well. |
This is very useful for situations when you want to add contextual meaning. For example, I have a <popover>
<h1>Not dismissable title</h1>
<button type="button" dismiss-popover>close</button>
<router-link to="..." dismiss-popover>go to...</router-link>
<button type="button" dismiss-popover @click="changeStatus">make offline</button>
</popover> In this way we have kind of contextual directive for Popover component. Popover can provide own instance for everybody inside and |
...or use a scoped slot. |
@LinusBorg I can't use scoped slot because then I need to bind <popover>
<template slot-scope="popover">
<h1>Not dismissable title</h1>
<button type="button" dismiss-popover>close</button>
<router-link to="..." dismiss-popover>go to...</router-link>
<button type="button" @click="changeStatus" @click="popover.close">make offline</button>
</template>
</popover>
Update: Also if I create a scoped to Popover directive I can't use it inside provided slots. shows error:
|
<button type="button" @click="changeStatus(); popover.close()">make offline</button>
That's a valid opinion to have - personally I think scoped slots are worthy to learn and should be in everyone's arsenal.
Well that shouldn't be an issue because we are trying to use scoped slots instead of this directive... |
@LinusBorg what about cases when Popover and its content in different components? Then you can't easily access popover to close the dialog by clicking or selecting somethid. Use case: <popover>
<canned-responses />
</popover> CannedResponses: <acordion>
<acordion-group>
<survey-list />
</acordion-group>
<acordion-group>
<prepared-responses-list />
</acordion-group>
</acordion> Then inside SurveyList I need to close Popover when user selects a survey. The only possible way currently is to emit event 2 times to reach Popover |
That would probably be a solid usecase for provide/inject if you don't want to pass down the scoped slot't callback 2 levels, but why through a directive? do it in the component the regular way. Personally I would probably still prefer the <popover>
<canned-responses slot-scope="{ close }" @selected="close"/>
</popover> Or even better, simply use a v-if in the parent: <popover v-if="show">
<canned-responses @selected="show = false"/>
</popover> Edit: By the way, jus to give some perspective: If it seems like we actively try to resist adding this feature, it's because we generally try to challenge each feature request thoroughly because we want to try and keep both filesize and API surface increases to a minimum. The codebase has already grown 20% since 2.0, we want to keep an eye on that. So for every new feature request that comes in and the potential is unclear, we challenge it by arguing against you, the ones arguing for it, to see if all options to solve this adequatly with the current API have been tried and found to not suffice. Only then will we consider a feature request to make it into core. |
Although this behaviour can be worked around, I agree that it would be useful to allow directives to access providers. My use case is for a wizard component where any other component can trigger an instructional animation which presents hints as tooltips that point to themselves. This could be achieved by wrapping each component in an Instruction component, but this is both verbose and unnecessary and would not easily allow for contextual help as in my opinion the instance component itself should be responsible for describing it's own help information, rather than the Instruction wrapper. I have written a library that side-steps these shortcomings, and it does work as described, but ideally would like to use the regular Vue logic to accomplish the same results. |
Till this feature will be released I found a example how to use vnode for this case https://codepen.io/Kradek/pen/zZmpNo |
Is there any workaround to get parent's provide in the directive? Here is my user story: The svg group has no width and height, so I build a provider(viewBox) on it. and it will be used on the the directive <svg-group viewBox={width: 200, height: 200}>
<text v-text-wrap>Its very long long long text to wrap......
</text>
</svg-group> BTW, Defining the viewbox provider as directive to better reuse code. So when do implement this feature? |
From the source there is no collected providers in import { DirectiveOptions } from 'vue';
export const M6yDirective: DirectiveOptions = {
bind(el, bind, vnode) {
// todo: native element hasn't component instance.
const vm = vnode.componentInstance;
// cache the providers to speedup.
const providers = vm._providers = getProviders(vm);
if (providers.viewBox) ....
},
update(el, bind, vnode){
const vm = vnode.componentInstance;
// use the cached vm._providers directly
vm._providers['viewBox']
},
};
function cloneExclude(dest, src, excludes: string|string[]) {
let vKeys = Object.keys(src);
if (typeof excludes === 'string') excludes = [excludes];
vKeys = vKeys.filter(value => -1 === excludes.indexOf(value));
vKeys.forEach(key => dest[key] = src[key]);
return dest;
}
function getProviders(vm) {
const providers:Object = {};
while (vm) {
if (vm._provided) {
cloneExclude(providers, vm._provided, Object.keys(providers));
}
vm = vm.$parent
}
return providers;
} |
I agree with this, however, I don't understand the reasoning for having inject/provide available in components but not directives. |
From ealier in the conversation by @posva
Possible to have some justification on this phrase ? I really don't get what the difference is. What I want to do is to expose register/unregister methods in a parent component that needs to be aware of some of its children elements whereabouts. Similar to form validation in vuetify that uses a registrable mixin, but where the children are very non-intrusive directives, not components. I really don't see why this pattern would be suitable for components but not directives. |
Just my two cents on this. I'm a Vue developer since 2017 (maybe more?) I recently started the transition of my company to Vue 3 and I'm trying to port some useful tools we've been using in Vue 2 to Vue 3 (Composition API) , one of these is the Flip Toolkit which was previously ported to Vue 2 here. I'm trying to do a pure composition API of such a library and, given setup functions don't have access to the Let me explain: the core of the Flip toolkit are Pseudo-code looks like this: Flipper import { Flipper } from 'flip-toolkit'
[...]
const flipInstance = new Flipper({
element: this.$el,
[...]
})
const addFlippedElement = () => {
flipInstance.addFlipped([...])
}
provide('addFlippedElement', addFlippedElement) Flipped const addFlippedElement = inject('addFlippedElement')
addFlippedElement({
element: this.$el
}) Usage Root of the app, maybe? <template>
<Flipper>
<router-view />
</Flipper>
</template> RandomComponent.vue (inside a router view) <template>
<Flipped>
<something />
</Flipped>
</template> The system supports nested The main concern of the
The main concern of the
A directive would be a much better fit for these kind of component in my opinion, the original vue-flip-toolkit uses Isn't a Directive more suited for accessing and manipulating DOM elements than a Component in these cases? In this particular case, given I didn't write the APIs of the library I'm using, I find that a directive that can |
It can be achieved now with
|
vnode.ctx.provides[InjectionKey]
|
|
What problem does this feature solve?
If a user uses a custom directive in their app in multiple places they might need to configure the directive in two or more different ways depending on the area of the app in which the directive is being used. If this configuration is used in many instances in one of these areas, providing this configuration on every instance becomes redundant and cumbersome.
What does the proposed API look like?
I think the provide/inject pattern would be a good solution to this. A user could add different configurations in the top level provider components and use the custom directive normally in their descendants.
I'm not confident on what the best place is for the actual injections to live in the directive hook arguments but there are some ideas in the code example above.
The text was updated successfully, but these errors were encountered: