Refs, v-bind, and v-model


Reactive Objects

Vue is a reactive web framework.

Vue allows us to declare reactive objects within the <script> section using the ref() function.  We’ll call reactive objects refs from here on out.  These refs have a property named value that can hold a value of any JavaScript type.  We can set and get the data stored in the value property similar to setting and getting the value of any JavaScript object.  Think of a ref as a special wrapper for an ordinary variable you would normally declare with const or let.

We can use a ref’s value property in computations in the <script> section’s code, or as the value for an element’s attribute in the <template> section, and in many other contexts (see below).  What makes refs special is that when the value stored in a ref changes, Vue recomputes and possibly re-renders the portion of the DOM that corresponds to the instance of the component with two exceptions:  1) Vue won’t necessarily re-render child components unless they themselves have refs that have changed and 2) Vue won’t re-render elements in the component that are excluded from re-rendering using the v-memo directive.

For example, in the code below, we declare a ref named count using ref() and initializes its value to 0.  We also have a function named increment() that adds 1 to the value of count each time the function is called.  Notice that when we modify the value of count we modify count.value.

We then bind the count ref to the contents of the <h1> element using mustache syntax: {{ count }}.  Note that we do not need to append .value after then name of the ref when used in a template.  This is because Vue automatically unwraps refs when used inside templates.

With these simple expressions, we’ve instructed Vue to monitor the value in count and automatically re-renders the contents of the <h1> element when the value of count changes.

[code language=”JavaScript”]
<script>
import { ref } from 'vue'
const count = ref(0)

function increment() {
count.value++
}
</script>

<template>
<h1>Count: {{ count }}</h1>
<button @click="increment">Increment</button>
</template>
[/code]

We can also write the button handler inline as shown below.  When we do, we again can omit .value when using the ref.

Note also that we can put any valid JavaScript expression that evaluates to a value inside the mustache syntax like {{ count * 2 }}.

[code language=”JavaScript”]
<script>
import { ref } from 'vue'
const count = ref(0)
</script>

<template>
<h1>Count: {{ count * 2 }}</h1>
<button @click="count++"</button>
</template>
[/code]

V-bind:

We may also want to bind a ref to an element’s attribute so that the value of the attribute will automatically change when the value of the ref changes.  Vue does not allow the use of mustache syntax inside HTML attributes, but has provided the v-bind: directive to declare a ref-to-attribute binding.

In the example below we have a ref that holds the name of a CSS class.  We bind the ref to the class attribute of an element so that when the value of the ref changes (by pressing the button), the element’s class attribute automatically changes and the element is re-rendered.

[code language=”JavaScript”]
<script>
import { ref } from 'vue'
const fontColor = ref('green')
</script>

<template>
<h1 v-bind:class="fontColor">{{ fontColor }}</h1>
<button @click="fontColor = (fontColor === 'red') ? 'green' : 'red'">Toggle</button>
</template>

<style>
.red { color: red }
.green { color: green }
</style>
[/code]

Note that v-bind does not create a 2-way binding.  If the value of the ref changes then the value of the attribute changes, but if the value of the attribute changes and not as a result of the ref changing, then the value in the ref does not change.

For example, if I add another button and in the handler for the button I clear the classList for the <h1> element, then the <h1> element will not have ‘red’ or ‘green’ in its class list, but the value of the fontColor ref will still be either ‘red’ or green’.  v-bind only establishes a one-way binding.

V-bind Shorthand

The shorthand syntax for v-bind is simply to preface the attribute with a colon (:) and omit v-bind.  For example, we can write the above h1 element as follows:

[code language=”JavaScript”]
<h1 :class="fontColor">{{ fontColor }}</h1>
[/code]

V-bind with Inline Style Properties

We can also use v-bind on a style property.  In the example below we bind a ref (fontColor) to the value of an inline style property (color).

[code language=”JavaScript”]
<h1 :style="{ color: fontColor }">Binding style attr</h1>
[/code]

We can also bind a ref to multiple style properties using an object.  In the example below we pass an object to ref() and bind the object returned by ref() to the <h1> element’s style properties defined in the object.

[code language=”JavaScript”]

<script>
const styleObject = ref({
color: 'orange',
fontSize: '30px'
})
</script>

<template>
<h1 :style="styleObject">Binding style – reactive</h1>
<button @click="styleObject.color = 'blue'">Blue</button>
<button @click="styleObject.color = 'red'">Red</button>
</template>

[/code]

V-bind with Boolean Attributes

If a ref is bound to a Boolean attribute then the attribute will exist on the element if the ref has a truthy value or is equal to the empty string.  Otherwise, the attribute will not be included on the element.

In the example below the button will have a disabled attribute if the value of the count ref is even; otherwise the button will not have a disabled attribute.

[code language=”JavaScript”]
<h1>Count is {{ count % 2 === 0 ? "even" : "odd" }}</h1>
<button :disabled="count % 2 === 0">Disabled if count is even</button>
[/code]

V-model

We know that v-bind only establishes a one-way binding between a ref and DOM elements on which the ref is used.  To illustrate this point again, if we have an input element and bind its value attribute to a ref then:

  1. when the value of the ref changes (press button), the contents of the input changes because Vue re-renders the component, but
  2. if we enter text in the input element then the value of the ref does not change.

[code language=”JavaScript”]
<script>
const title = ref('Nightmare on Elm Street')
</script>

<template>
<input type="text" :value="title" v-memo="[title]"> <br>
<span>Ref: {{ title }}</span> <br>
<button @click="title += String.fromCodePoint(0x1f52a)">Append to ref</button>
</template>
[/code]

The use of v-memo in the <input> element above informs Vue to only re-render this particular input element if the value in the title ref changes.  I add this so that we modify the other refs in this page, this <input> element won’t be re-rendered thus resetting the contents of the input to the value in the title ref.

When working with forms its often useful to bind a ref to the value attribute of an input element so that we can initialize the value in the input and also make it so that whatever the user types text in the input the text is also saved in the ref.  We can accomplish this by adding an event handler for the input event.

[code language=”JavaScript”]
<script>
const username = ref('freddy')
</script>

<template>
<span>ref: {{ username }}</span> <br>
<input type="text" :value="username" @input="e => username = e.target.value">
</template>
[/code]

This pattern is used often so Vue added the v-model directive.  The v-model directive binds a ref to the value attribute of an input and binds the element’s value property to the ref thereby creating a 2-way binding.

[code language=”JavaScript”]
<script>
const address = ref('123 Elm Street')
</script>

<template>
<span>ref: {{ address }}</span> <br>
<input type="text" v-model="address"> <br>
</template>
[/code]