Dynamic component and 2 way binding


#1

I have a form that creates a dynamic list of component input elements. How do I create a binding outside of the components?

Here’s a twiddle that will show the problem: Twiddle

The code:

valueList: [1, 2, 3, 4]

{{#each valueList as |v|}}
  {{component 'x-thing' val=(mut v)}}
  Value outside: {{v}}
{{/each}}

Component:

<button {{action (action (mut val) 10)}}>
  Change to 10
</button>
Val in component: {{val}}

#2

I think the second example in your twiddle is doing what you want it to, by explicitly opting in to two-way binding. That’s a totally fine way to do it.

The alternative is to pass the value and an action for changing the value separately into each component.

{{component 'x-thing' val=v change=(action (mut v)) }}

Component:

<button {{action change 10}}>
    Change to 10
</button>

#3

Forgot to fork the twiddle. Here’s the correct one: Correct twiddle

Can’t get this working with your code or other code.


#4

Ah, I see. Your issue is that you’re trying to directly update members of a list. There is a general issue here that isn’t Ember-specific, it’s about Javascript value-vs-reference.

Consider an example snippet like:

let list = ['a', 'b', 'c'];
for (let item of list) {
  changeItemToD(item);
}
console.log(list);

Imagine how you could write the function changeItemToD such that the console.log prints ['d', 'd', 'd']. If you think about it for a bit, you’ll realize it’s impossible. No matter what the changeItemToD function does, it can’t alter the list because it doesn’t even have a reference to the list.

This is analogous to what’s happening in your example. Each component is only receiving one of the items from the list, so it can’t actually change the list itself.

Here’s a forked version of the gist that works. I’m passing actions down to the components that they can use to actually modify the list. In this case, I kept a native Array like you were using, so in order to tell Ember that it changes I’m recomputing a new array via map and replacing the original value with this.set.

And here is an alternate implementation that switches to an Ember Array so we can use replace to tell Ember that only one item in the list is changing.

Both these implementations are equally good ways to do it. I tend to favor the native Array when the cost of recomputing a whole array is low (which it often is), because there’s less ember-specific API to learn (all you need to know about is set).


#5

Thanks for the explanation. I totally get it now.

I simplified my twiddle to get it working but removed the object which passed object references which skips the whole problem.

Here is an updated twiddle with a more real-life example of a dynamic form that doesn’t get affected by the direct value binding and uses an object value instead.