Vue.js logo

Vue.js – Binding a Component in a v-for loop to the Parent model

Davide Barranca —  — Leave a comment

Learning Vue.js is fun – if I run into a problem that has taken me some head scratching time to solve and/or and no easy Stack Overflow answer, why not writing a blog post for you and my future self? 🙂

Today’s stumbling block is bi-directionally binding of a Component (v-model), to the root data object – being the Components generated in a v-for loop. Sounds unclear? Think about a lot of instances of a Component containing, say, checkboxes or radiobuttons, automatically generated from an array. It’s a quite frequent scenario, at least in my projects, so let’s have a look.

Checkboxes

I’m going to show you a couple of different setups and solutions. First initial arrangement is as follows:

While the Javascript and the Vue code is:

In this scenario it is particularly important for me that the v-model of each checkbox is bound to the boxes array of objects, so that other functions are able to refer to them (say, passing their values down to the Photoshop JSX layer).

But clearly the checkboxes cry to be implemented as Vue Components! So let’s do that – first in the HTML as a template – that, by now, is mostly an empty placeholder:

We need to register the Component in the JS, so:

Please note that I’m using both camelCase and kebab-case, as the Vue.js doc suggests. I’ve declared three props:

  • boxIndex: will be 0, 1, 2, 3… 9.
  • boxName: the checkbox string label, like "one", "two", etc.
  • boxStatus: will be the Checkbox checked status (either true or false), and will be bound to the data.boxes Object.

OK, how do we implement the v-for loop, so that all the ten Components instances are rendered on the page? A good starting point is:

So far so good, nothing that you can’t figure by yourself. Here comes the tricky part, have a look.

The checkbox value is {{boxIndex}}, that is passed as a prop using the shorthand syntax :box-index (which stands for v-bind:box-index) and assigned to the special property provided by the v-for loop $index. Note here camelCase and kebab-case.

Similarly, {{boxName}} and {{boxStatus}} are passed in the loop using :box-name and :box-status, and the latter is used to fill the checked property of the input tag. This way, the component “knows” whether to be initialized as checked / unchecked depending on the status property of the respective object in the boxes array.

I’ve also added as a label for each box component a string containing them all, and at the bottom the JSON version of the boxes object to check if binding works properly.

We’re slowly getting there: each checkbox shows its index, name and status, and the one labelled “four” correctly initializes itself as “checked”. But if you try clicking them, neither their status, (true/false in the label) nor the root boxes object updates.

This binding requires a couple of new lines of code, one in the Component’s template – i.e. a click handler:

and the corresponding function in the Components declaration in the JS:

This triggers a change in the Component’s boxStatus property, so that when you click each checkbox, you see that its own status (logged in the label) changes accordingly.

Which is cool, but there’s just one piece of the puzzle missing – can you find it?

The Component’s boxStatus is updated, but the Component (or better: each Component) has an isolated scope of its own! In fact, the boxes object, logged as JSON is not changed (every status is false but one).

In order to make a two way binding, you need to add the binding type modifier .sync to the prop, like:

This correctly set everything: each Component is initialized with the boxes object, and two-ways bound to it – the same way it was just before refactoring with Components. See the full code in the JsBin below:

JS Bin on jsbin.com

(link to the bin on a larger page here).

RadioButtons

Following the same idea, let me show a different solution for RadioButtons, using events. The component looks like that:

You see that there are channelName, channelIndex and channelChecked props, with a similar changeRadio function for the click handler. The loop is based upon a channels array, see the JS side:

Clicking on each radiobutton component now dispatches a 'radioClick' message (defined in the component’s methods object, and carrying the channelName as the payload), handled by the root Vue instance (see both events and methods objects).

The handleRadioClick then adjust the checked properties on each object within the channels array, that the radiobuttons are bound to – please note that because of this, there’s no need to add .sync in the template now. See it in action here:

JS Bin on jsbin.com

(link to the bin on a larger page here).

That’s it! It took me a while to figure this out. Possibly the second example – where the parent (root, here) Vue instance is in charge of updating the data object, to which the Component is bound – is “more proper” than the first one – where the Component did it by itself. Hope this helps! Ciao.

Print Friendly
Share

No Comments

Be the first to start the conversation.

Leave a Reply

Text formatting is available via select .

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url=""> 

*
*