Vue: [Feature request] Add a reference to the v-model in the directive hook signature

Created on 30 Oct 2016  路  7Comments  路  Source: vuejs/vue

Currently with Vue 2, when you use a directive on an element, you only have 2 options that I know of that allows you to update the v-model foo from within the directive bind functions (and other hooks).

The first option is to modify your Html template and pass the v-model name as a parameter, like :

<input type="text" v-my-directive="'foo'" v-model.number="foo">
//Then use that inside the directive hooks with :
vnode.context[bindings.value] = 42;

The problem with this approach is that first it's not DRY, the v-model name is right there, why repeat it? It's also error-prone, too verbose and more importantly, poses difficulties when used in a v-for loop.

The second option, far better in my opinion, is to keep the Html simple and semantic :

<input type="text" v-my-directive v-model.number="foo">

All you have to do then is accessing the v-model name via the vnode.data.directives variable which lists all the directives attached to the element, v-model being one of them. From there you have to iterate on that array until you find the model named directive. Lastly, you can access the v-model name in its expression attribute.

I created some helpers function to do just that ;

export function findVModelName(vnode) {
    return vnode.data.directives.find(function(o) { //Search the v-model name attached to the element
        return o.name === 'model';
    }).expression;
}

export function setVModelValue(value, vnode) {
    vnode.context[findVModelName(vnode)] = value;
}

This works well also in v-for loops, or when you do not know the v-model name beforehand.

My feature request would simply by to modify all the directive hook function signatures to add a reference to the v-model object. The new signature would be like :

bind(el, bindings, vnode, oldVnode, vModel) {}

//you would then be able to modify the v-model attached to the element the directive is declared on just by using :
vModel = 42; //Doing that would automatically update the v-model accordingly

I think that would help a lot, what do you think?

You can see an example where I needed to access the v-model in the following forum thread

All 7 comments

This could be achieved in 1.0 directives with this.set(), but the decision to remove it was intentional.

In 2.0, directives are meant to be used for low-level DOM access only. Things like meddling with v-model functionality should explicitly not be their job anymore.

In most cases where you want to do that with a directive, You should rather use a component instead.

If you have a specific situation that you need help with, we're happy to help you out on forum.vuejs.org

Well, components are powerful indeed, but lacks one thing entirely ; modularity.

If for instance I want to add multiple behaviors to an element (ie. select on focus, select on 'escape' if the value hasn't changed, call a function on change & blur, make an input cancellable, manage an input mask via a third party plugin, etc.), using a component, I have to mix all the logic from each behavior into the same onInput/onChange/onFocus callbacks.
This gets messy very quickly.

Now, how can you not _repeat yourself_ when you then have another element that only needs a subset of those behaviors? With components, if I understood correctly, you'd then have to recreate those onInput/onChange/onFocus handlers, but with slight variations. Not keeping it DRY, at all.

On the other hand, with directives you just _tag_ any element so that it gets the desired behavior.
That way you keep the logic in only one place, the html template is more semantic and readable, and hence more maintainable. Sounds good to me.

So, I think my 'feature request' is still valid since I unsuccessfully fail to see the advantage of components over directives for this kind of uses. Perhaps I'm wrong, and, if possible, I'd be glad to be corrected with a more in-depth explanation on _how directives are evil (and not just because we want to be different than angular)_ ;)

Lastly, since the possibility of changing the v-model from a directive still exists, albeit being a bit convoluted, why not just add an additional parameter that would not break any legacy code, and just save some of us some hassle ?

On a side note, unfortunately [forum.vuejs.org] is not helpful at all (cf. my question on this subject from eight days ago that remains without answers to this day), or perhaps my use cases are all edge cases?...

This is a pretty low-level feature, and as you've demonstrated, you already have working code that achieves what you want. Our general philosophy is leaving such cases in userland unless it's an extremely common use case.

Adding to that: many of those usecases you mentioned _can_ and _should_ be done with directives. And most of what you mentioned would not require the kind of reference that you asked here for.

I would rather think that it would be odd/problematic to have more than one directive writing to the model, anyway (unless for very different events, e.g. your "cancelable" example). So you could only ever use one directive that accesses the model, otherwise they would get in each other's way.

So the point is: when your behaviour requires access to the model, make it a custom component, and add other directives (e.g. Something like v-focus) to that component when you need them:

<masked-input v-focus v-whatever></masked-input>

@AlexandreBonneau Another note on this:

Even if we provided an official API to set the value that the v-model expression references, this would mean that any directive that uses this method would be dependent on v-model being present.

And the problem with that is that in many cases, you do _not_ want to or can't use v-model, e.g.:

  • Custom Input Components
  • When using vuex.
  • You use some other plugin/lib that works with the input and conflicts with v-model
  • and sure many more

In those cases, you would rather add an event listener (v-on:input="callback") so there's not expression to set the value for, and the directives would break.

If you have a low-level functionality as your examples from above, you might be better of to solve this on this low level of DOM methods, e.g. by writing to the value of the input and then manually triggering the input (DOM-)event:

var event = new Event('input', {bubbles: true})
el.value = 'Test'
el.dispatchEvent(event)

https://jsfiddle.net/g0xa5gw4/1/

If you are concerned about browser compatibility (new Event() won't work in IE <11), there's a couple npm packages that provide compatibilty for this for older IE versions

Thank you for your input on this _(pun intended ;))_.

I'll see how I can update my code to use this way of doing low-level inputs.

Also about the v-model reference in the directive hook signatures, I imagine you _could_ just set vModel to null by default when there are no v-model attached to an element on which the directive is declared though.
The remaining problem for maintainability is still being able to make sure multiple directives play well together if they all access the v-model...

Also about the v-model reference in the directive hook signatures, I imagine you could just set vModel to null by default when there are no v-model attached to an element on which the directive is declared though.

I was thinking about redusability, and compatibility.

You would often be in a situation where you start with using v-model, and later (have to) refactor to use an @event instead -. At which point all those directives relying on the v-model expression would stop working

Was this page helpful?
0 / 5 - 0 ratings

Related issues

aviggngyv picture aviggngyv  路  3Comments

loki0609 picture loki0609  路  3Comments

hiendv picture hiendv  路  3Comments

julianxhokaxhiu picture julianxhokaxhiu  路  3Comments

loki0609 picture loki0609  路  3Comments