Skip to content
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

Feature: multiple v-model bindings #4946

Closed
alexmakeev opened this issue Feb 16, 2017 · 17 comments
Closed

Feature: multiple v-model bindings #4946

alexmakeev opened this issue Feb 16, 2017 · 17 comments

Comments

@alexmakeev
Copy link

alexmakeev commented Feb 16, 2017

Sometimes it is needed to build complex fill-in forms depending on multiple objects, that could be changed inside the component and should be updated at parent component.

Example of possible use (there are many such cases in complex graph-based single page apps):
<oreder-card v-model:profile="user.profile" v-model:order="order"></order-card>

Current approach:
<oreder-card :profile="user.profile" v-on:input_profile="user.profile = arguments[0]" :order="order" v-on:input_order="order = arguments[0]"></order-card>

@yyx990803
Copy link
Member

Component v-model is designed for single value input components that attend to similar use cases for native input elements.

For a complex component that manages the synchronization of more than one values, explicit prop/event pairs is the proper solution. In this particular case I don't think the saved keystrokes are worth the added complexity of additional syntax.

@Justineo
Copy link
Member

Justineo commented Apr 5, 2017

@yyx990803

If we have a Dialog component, the current prop/event pairs looks like this:

<dialog :open="messageOpen" @close="messageOpen = $event.value">

It's just one prop, we'll have much more for a rather complicated component.

Let's look at how component libraries are doing this right now.

Element and iView usually use props to initialize a data field (like .once) and then watch some of the props to update local data and to respond when parent component's data updates (like props without immutability). Child components may $emit various events to notify some props have been changed and request their parents to update data. This works but parent component's data may be inconsistent with the child's view unless the prop change event is handled properly, and the binding syntax is explicit but redundant, comparing to .sync.

For some components, there might be a "major" prop which covers most of child component's logic. eg. For Dialogs, this prop would be open. In such cases the "major" prop may be designed to comply with the v-model interface thus parent components can bind v-model to it. As you said, v-model is designed for single value components but as you can see, people are actually using it as a restricted version of .sync (works for only one prop).

Regarding to Vue's current design, I'd say is not proper but the most popular UI libraries are using this paradigm. People use v-model on non-input components just because they don't have .sync.

If only prop/event pairs are the only proper way, then why v-model is designed in the first place? Just because it has a "major" prop which matches scenarios like form validation/submission?

I understand why .sync is removed but I don't think it's a good idea for a component library to introduce solutions like vuex because this will restrict the way how people use it.

Under the premises that .sync won't come back and v-model is designed for single-value inputs, I'd ask if it is proper to let custom directives to do this. For example, we design custom directives like this:

<dialog v-sync:open="messageOpen">

Which works as syntactic sugar for

<dialog :open="messageOpen" @propchange="dialogUpdate">
methods: {
  dialogUpdate(prop, value) {
    if (prop === 'open') {
      this.messageOpen  = value
    }
  }
}

@yyx990803
Copy link
Member

yyx990803 commented Apr 5, 2017

@Justineo I think this is more like a compile-time de-sugaring. It's actually possible by adding custom compilation modules to vue-loader, but the drawback is your components can then only be compiled with the extra build configuration.

Alternatively, we can bring .sync back but instead of implicit two-way binding it also de-sugar into prop/listener pairs...

@Justineo
Copy link
Member

Justineo commented Apr 5, 2017

I think bringing .sync back as sugar will already please most library developers. AFAIK quite a few Vue developers are stuck with 1.x just because they rely on .sync so much. For me we are now developing a UI library and I'm looking into how others are solving this problem. The paradigm most libs are using seems weird to me but they have no choice.

Even if we cannot bring back .sync directly, I still hope custom directives or something like this can provide similar feature. But right now it seems not possible to replace a prop/event listener pair with a custom directive (prop bindings are already compiled into the render function and we have no runtime API to add new ones).

@yyx990803
Copy link
Member

@QingWei-Li @icarusion what are your opinions on this? Do you run into this problem often?

@QingWei-Li
Copy link
Contributor

We have had the same problem. Actually, we have implement v-sync directive, but not good. I think it's a good idea if we can have built-in v-sync directive. @Leopoldthecoder What do you think?

@Leopoldthecoder
Copy link

I admit using v-model in a non-form component is a bit strange.

When designing Element's Dialog, at first we tried to bring back .sync by implementing a custom v-sync directive, so that we can

<dialog v-sync:visible="myVisible">

But it couldn't work because a component is not allowed to modify its own props.

Then there is prop/listener pair. The disadvantage of this approach is that the user has to write an event listener to manage his data. We decided it didn't make much sense for a component library.

So finally we chose v-model. It kinda breaks the semantics but perfectly does the job.

Generally we didn't run into this problem quite often when developing Element, perhaps just Dialog and Pagination. But having a built-in v-sync that compiles to prop/listener pairs seems really handy. I'd be happy to see it happening.

@yyx990803
Copy link
Member

yyx990803 commented Apr 6, 2017

The primary reason for removal of the old .sync is it encourages the child to implicitly affect parent state. But if we can ensure that every time the child affects parent state, it's always by explicitly emitting an event, then we can bring back .sync in the form below:

<test :foo.sync="bar" />

is expanded into the following at compile-time:

<test :foo="bar" @update:foo="value => bar = value" />

So the parent doesn't need to define a method just to do the mutation, but the child needs to emit an event instead of just mutating the prop:

this.$emit('update:foo', newValue)

Since this is encapsulated inside the child implementation, consumers of the library can just use .sync like the old days.

@Justineo
Copy link
Member

Justineo commented Apr 6, 2017

This looks great. Can't wait to see it back. 👍

@Molunerfinn
Copy link

Nice!

@icarusion
Copy link

@jingsam

@binsinger
Copy link

@yyx990803
this.$emit('update:foo', newValue)
不明白怎么用这行代码,在哪里调用?有没有完整的例子看看

@599316527
Copy link

@binsinger 就是在组件里需要更新 foo 的地方触发一个事件,用法看文档

@lishichao1002
Copy link

lishichao1002 commented Jul 9, 2018

In Angular, We can use [()] to implement to way bindings ignore bindings count. May be it will be awesome If vue offer this feature

@posva
Copy link
Member

posva commented Jul 9, 2018

Vue has this feature, it's the sync modifier on v-bind

@DesselBane
Copy link

More info about .sync
https://medium.com/front-end-weekly/vues-v-model-directive-vs-sync-modifier-d1f83957c57c

@obedparla
Copy link

obedparla commented Nov 13, 2020

For anyone landing here and wondering: This is available in Vue 2.3+: https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests