Vue.js 3 Data & One-way Databinding Tutorial

In this Vue tutorial we learn one-way databinding with String Interpolation, temporarily storing data in the config option and binding to HTML attributes.

We also cover binding to CSS classes, how to output raw HTML and skip compilation on specific content.

Lesson Video

If you prefer to learn visually, you can watch this lesson in video format.

Lesson Project

If you want to follow along with the examples in this lesson, you will need an app generated by the Vue CLI . If you already have one from the previous lesson, you can use it instead.

The data config option

Any data we need is stored in the data option of the component’s config object.

This object key takes a function as its value.

Syntax: data option
<script>
export default {
  data: function() { }
}
</script>

In modern Javascript (ES6+) when the value is a function, we can use the method shorthand syntax where the key becomes the function.

Syntax: ES6 shorthand
<script>
export default {
  data() { }
}
</script>

The function returns an object where we store our data as key:value pairs, separated with a comma.

Example: data return
<script>
export default {
  data() {
    return {
      fName: 'John',
      lName: 'Doe'
    }
  }
}
</script>

These key:value pairs are also known as data properties or data keys. Any time during the course when we mention data properties, we’re talking about the data returned here.

What is databinding?

Databinding is the communication between the View (template) and the Logic (script) of the component.

There are three types of databinding in Vue.

  1. One-way databinding from the Logic to the View. Data from the Logic is output in the View with String Interpolation or Attribute binding.
  2. One-way databinding from the View to the Logic. We react to events from the View, like a button click, with Event binding.
  3. Two-way databinding between the Logic and the View. Combining the two above, we can react to events and output something at the same time.

One-way databinding with String Interpolation with moustache syntax

If data is hardcoded into the HTML of the template, we will constantly need to update it manually whenever it changes. Instead, we want to update the data in a single place and have Vue take care of updating the HTML for us.

This is easy to do with a process called String Interpolation. We can reference the data property in the template and Vue will replace the reference with the actual data.

To reference a data property, we wrap it in double open-and-close curly braces (known as moustache syntax).

Syntax: moustache syntax
{{ property_name }}

We can only use string interpolation in the <template> block, and only as a value for an HTML element.

As an example, let’s see the code from when we made our first app .

Example: src/App.vue
<template>
  <h1>Hello, {{ name }}</h1>
</template>

<script>
export default {
  data() {
    return { name: 'John' }
  }
}
</script>

We return a data property called “name” and then reference it with moustache syntax in the h1 element in the template.

We can also use more complex structures like objects and arrays as data properties. Objects can be accessed with dot notation and arrays with their indexers.

Example: complex data structures
<template>
  <!-- reference objects with dot notation -->
  <h2>Hello, {{ userObj.fName }} {{ userObj.lName }}</h2>

  <!-- reference arrays with the indexer -->
  <h2>Hello, {{ userArr[0] }} {{ userArr[1] }}</h2>
</template>

<script>
export default {
  data() {
    return {
      userObj: { fName: 'John', lName: 'Doe' },
      userArr: ['John', 'Doe']
    }
  }
}
</script>

One-way databinding to HTML attributes with v-bind

As mentioned earlier, we can only use string interpolation as a value inside an element. If we try to use it for an attribute value, things will break.

Example: src/App.vue
<template>
  <a href="{{ link }}">Vue {{ version }} API Reference</a>
</template>

<script>
export default {
  data() {
    return {
      version: 3,
      link: 'https://vuejs.org/api/'
    }
  }
}
</script>

The link displays on the page, but if we click on it, it doesn’t take us to the Vue API reference.

Instead of using the value of the link data property like we expect, Vue displays the curly braces and property name as a static string.

So the link will point to http://localhost:8080/%7B%7B link %7D%7D instead of https://vuejs.org/api/ .

To bind to an attribute, Vue gives us the v-bind directive.

We add the v-bind keyword in front of the attribute we want to bind, with a semicolon separating them. Then we reference the data property as the attribute’s value.

Syntax: v-bind
<!-- in a self-closing element -->
<element v-bind:attribute="data" />

<!-- in a open-and-close element -->
<element v-bind:attribute="data"></element>

tip Vue allows us to bind data to any attribute on an element. These include attributes like id , style , href , and even boolean attributes like disabled .

Let’s fix our earlier example to use the v-bind directive on the href attribute instead of string interpolation.

Example: src/App.vue
<template>
  <a v-bind:href="link">Vue {{ version }} API Reference</a>
</template>

<script>
export default {
  data() {
    return {
      version: 3,
      link: 'https://vuejs.org/api/'
    }
  }
}
</script>

If we run the example and inspect the element in the browser’s dev tools, we can see that the link now works correctly.

Output:
<a href="https://vuejs.org/api/" title="https://vuejs.org/api/" target="_self">Vue 3 API Reference</a>

As mentioned in a tip earlier, we can bind to boolean attributes like disabled . The difference between regular attributes and boolean attributes is in how Vue renders the state.

To demonstrate, let’s create a boolean data property called “isDisabled” and bind it to the disabled property of a button element.

Example: src/App.vue
<template>
  <button v-bind:disabled="isDisabled">Boolean Attribute Binding</button>
</template>

<script>
export default {
  data() {
    return {
      isDisabled: true
    }
  }
}
</script>

If we run the example, the button will show a disabled state in the browser. When we inspect the element in the browser’s dev tools, we can see that Vue has added the disabled property.

Output:
<button disabled="">Boolean Attribute Binding</button>

Now let’s change isDisabled to false.

Example: src/App.vue
<template>
  <button v-bind:disabled="isDisabled">Boolean Attribute Binding</button>
</template>

<script>
export default {
  data() {
    return {
      isDisabled: false
    }
  }
}
</script>

The button will be enabled in the browser and we can click on it. If we inspect it again in the browser’s dev tools, we can see the disabled property has been removed completely.

Output:
<button>Boolean Attribute Binding</button>

So in the case of boolean attributes, where their existence implies a true value, the attribute will not be included in the render.

How to bind to the class attribute

Because styling is such a big part of an application, it’s worth it to dedicate a section to learning how to bind multiple classes, or bind them conditionally.

It’s especially useful if your styling is utility-based, or you use a utility-based framework like TailwindCSS .

It’s the same as normal attribute binding, but we reference a data property that contains the name of the class we want to add.

Syntax:
<template>
  <p v-bind:class="dataProperty">Lorem ipsum dolor</p>
</template>

<script>
export default {
  data() {
    return {
      dataProperty: 'class-name'
    }
  }
}
</script>

<style>
  .class_name { }
</style>

That means we can programmatically change the data property to another class name and Vue will handle updating the attribute.

As an example, let’s say we want to show the user a valid/invalid message when they enter something into an input field. But we want the valid text color to be green, and the invalid text color to be red.

We’ll define two CSS classes with the appropriate colors and assign one as the default value to a data property. Then we bind that data property to the class attribute in the template.

Example: src/App.vue
<template>
  <p v-bind:class="status">Lorem ipsum dolor</p>
</template>

<script>
export default {
  data() {
    return {
      status: 'invalid'
    }
  }
}
</script>

<style>
  .valid   { color: forestgreen }
  .invalid { color: crimson     }
</style>

If we inspect the paragraph in the browser’s developer tools, we’ll see that the invalid class was applied.

Output:
<p class="invalid">Lorem ipsum dolor</p>

If we change “status” to the valid class, it will apply that to the paragraph.

Combining static and dynamic classes

If we already have other classes on an element, we don’t bind to that attribute. Instead, we bind to a separate class attribute and Vue will combine them for us.

Syntax:
<element class="static-classes" v-bind:class="dataProperty" />

To demonstrate, let’s add a static class to our paragraph element.

Example: src/App.vue
<template>
  <p class="text-22" v-bind:class="status">Lorem ipsum dolor</p>
</template>

<script>
export default {
  data() {
    return {
      status: 'invalid'
    }
  }
}
</script>

<style>
  .valid   { color: forestgreen }
  .invalid { color: crimson     }
  .text-22 { font-size: 22px    }
</style>

If we inspect the element in the browser’s developer tools, we’ll see the two classes were combined.

Output:
<p class="text-22 invalid">Lorem ipsum dolor</p>

Vue combines classes in the same order we define them. If we do the binding before the static classses, that’s the order in which they will be applied.

Let’s change our example and swap the static class list with the dynamic binding.

Example: src/App.vue
<template>
  <p v-bind:class="status" class="text-22">Lorem ipsum dolor</p>
</template>

<script>
export default {
  data() {
    return {
      status: 'invalid'
    }
  }
}
</script>

<style>
  .valid   { color: forestgreen }
  .invalid { color: crimson     }
  .text-22 { font-size: 22px    }
</style>

This time when we inspect the element, the invalid class will be first.

Output:
<p class="invalid text-22">Lorem ipsum dolor</p>

How to conditionally bind a class

You will often need to bind certain classes based on the results of a conditional evaluation. For example, you may want to close a modal or menu, or to change the color of an input when its value is invalid.

Vue allows basic Javascript expressions in most of its directives, including v-bind. We can use the ternary expression to conditionally check which class to add.

Syntax:
<element v-bind:class="condition ? 'true_class' : 'false_class'" />

We write the ternary statement as follows.

  1. First, we specify the condition followed by a question mark. This can be something simple like checking if a boolean data property is true or false.
  2. Then we specify the class name that we want to add if the expression evaluates to true, followed by a : (colon).
  3. Finally we specify the class name we want to add if the expression evaluates to false.

The class names must be wrapped in quotes otherwise Vue will not add them.

To demonstrate, let’s add a boolean data property called “isValid” to our example, with a default value of true. In our binding we’ll use a ternary to add the classes based on the value of isValid.

Example: src/App.vue
<template>
  <p v-bind:class="isValid ? 'valid' : 'invalid'">Lorem ipsum dolor</p>
</template>

<script>
export default {
  data() {
    return {
      isValid: true
    }
  }
}
</script>

<style>
  .valid   { color: forestgreen }
  .invalid { color: crimson     }
</style>

Because isValid is true, the ternary evaluates to true and the valid class is added. If we change isValid to false, the invalid class will be added.

How to bind multiple classes with an array

If we want to bind multiple classes, we can use more complex data structures like arrays or objects. Vue will add those classes based on the order of their definition inside the array.

We can add them directly in the binding as a value, or store them in a data property and reference the property.

Syntax:
// direct
<element v-bind:class="['class1', 'class2', 'class3']"></element>

// by reference
<element v-bind:class="classArray"></element>

data() {
  return {
    classArray: ['class1', 'class2', 'class3']
  }
}

If we need to add a class in the array conditionally, we add the entire ternary expression as a value.

Syntax:
// direct
<element v-bind:class="['class1', condition ? 'true_class' : 'false_class']"></element>

// by reference
<element v-bind:class="classArray"></element>

data() {
  return {
    classArray: ['class1', condition ? 'true_class' : 'false_class']
  };
}

To demonstrate, let’s add two classes called “bold” and “italic” to our example. Then we’ll add them to an array with the isValid ternary as the last value.

Example: src/App.vue
<template>
  <p v-bind:class="['bold', 'italic', isValid ? 'valid' : 'invalid']">
    Lorem ipsum dolor
  </p>
</template>

<script>
export default {
  data() {
    return {
      isValid: true
    }
  }
}
</script>

<style>
  .bold    { font-weight: bolder }
  .italic  { font-style:  italic }
  .valid   { color: forestgreen  }
  .invalid { color: crimson      }
</style>

The text should now be bold, italic and colored green.

As mentioned earlier, the classes are added in the same order that they exist in the array. If we inspect the element in the browser’s dev tools, we can see that they are.

Output:
<p class="bold italic valid">Lorem ipsum dolor</p>

How to bind multiple classes with an object

Because an object uses key:value pairs, it works slightly different than an array.

In this case, the key is the class name that we want to add and the value is the condition for the class to be applied.

If we want to add the class but it doesn’t rely on a condition, we can simply specify true as the value and it will be added.

Just like an array, we can add the object directly in the v-bind directive, or store it as a data property and reference that property in the directive.

Syntax:
// direct
<element v-bind:class="{
  class: condition
}"></element>

// by reference
<element v-bind:class="classObject"></element>

data() {
  return {
    classObject: {
      class: condition
    }
  }
}

To demonstrate, let’s change our earlier example to use an object instead of an array. As before, we’ll define it directly in the binding.

We want the bold and italic classes to always be present, so their values will just be true. The valid and invalid classes can do a simple boolean check and to get a different outcome this time, we’ll change isValid to false.

Example:
<template>
  <p v-bind:class="{
    bold: true,
    italic: true,
    valid: isValid,
    invalid: !isValid
  }">Lorem ipsum dolor</p>
</template>

<script>
export default {
  data() {
    return {
      isValid: false
    }
  }
}
</script>

<style>
  .bold    { font-weight: bolder }
  .italic  { font-style:  italic }
  .valid   { color: forestgreen  }
  .invalid { color: orangered    }
</style>

The text should now be bold, italic and red.

The object approach will also add the classes in the same order that they exist in the object. If we inspect the element in the browser’s dev tools, we can see that they are.

Output:
<p class="bold italic valid">Lorem ipsum dolor</p>

Arrays vs Objects: Which to use when

You can use whichever one you prefer, but stay consistent throughout your project.

We prefer the object approach for the following reasons.

  • It allows us to use the boolean as a condition to quickly and easily add or not add a class while testing.
  • We try to avoid the ternary operator where possible because it can be abused to build unreadable expressions.

The v-bind directive shorthand

Because the v-bind directive is so frequently used to apply dynamic behavior to the template, the Vue team decided to create a shorthand syntax for it.

We can omit the v-bind keyword and just use the : (colon) followed by the attribute.

Syntax:
<element :attribute="data" />

To demonstrate, let’s create a link to the Vue 3 API Reference but use the shorthand to bind to the href attribute.

Example: src/App.vue
<template>
  <a :href="link">Vue {{ version }} API Reference</a>
</template>

<script>
export default {
  data() {
    return {
      version: 3,
      link: 'https://vuejs.org/api/'
    }
  }
}
</script>

If we run the example in the browser, the link will work as expected.

Raw HTML Output with v-html

In some cases we may need to output data that includes HTML tags. We can’t do that with string interpolation because Vue converts any special characters into web-safe characters to help protect against security concerns like cross-site scripting.

So the Vue team decided to make a dedicated v-html directive to allow HTML characters.

To use it, we specify the v-html keyword on an element and reference a data property that contains the raw HTML we want to output.

Vue will access the element’s innerHTML behind the scenes and add our data.

Syntax: v-html
<element v-html="data_reference" />

As an example, let’s output a simple message with raw HTML into a paragraph element in the template.

Example: src/App.vue
<template>
  <p v-html="msg"></p>
</template>

<script>
export default {
  data() {
    return {
      msg: '<strong>Hello Vue</strong>'
    }
  }
}
</script>

Vue will not convert the HTML to a static string and the text on the page will show as bold.

How to skip content compilation with v-pre

We can tell Vue to skip compilation on an element that doesn’t contain any Vue directives with the v-pre directive. Essentially, it’s the opposite of the v-html directive.

To use it, all we have to do is attach the v-pre directive to the element we want to skip compilation for. It doesn’t expect any value or expression.

Syntax: v-pre
<element v-pre />

As an example, let’s say we are creating a tutorial that shows the user how to use moustache syntax. We want to render the double curly braces in one element, but also render the normal behavior in another.

We can just add v-pre to the element where we want to show the curly braces.

Example: src/App.vue
<template>
  <p>Moustache syntax wraps the data property in double curly braces.</p>
  <p v-pre>Example: {{ name }}</p>

  <p>The resulting output will be the value of the data property.</p>
  <p>Example: {{ name }}</p>
</template>

<script>
export default {
  data() {
    return {
      name: 'John Doe'
    }
  }
}
</script>

note Vue doesn’t render the v-pre content in an HTML <pre> tag, it just skips compilation of the element so that it’s rendered as text.