Vue.js Directives
Introduction
Directives are one of the most powerful features in Vue.js. They are special attributes with the v-
prefix that tell Vue to do something specific to a DOM element. Directives are what make Vue.js applications dynamic and reactive.
In this tutorial, we'll explore Vue.js directives in depth, understand how they work, and learn how to use them effectively in your applications.
What are Directives?
Directives are special tokens in the markup that tell the library to do something to a DOM element. In Vue, directives are prefixed with v-
to indicate that they are special attributes provided by Vue.
A directive's job is to reactively apply side effects to the DOM when the value of its expression changes.
Core Directives in Vue.js
Let's explore the most commonly used directives in Vue.js:
v-bind
The v-bind
directive is used to dynamically bind one or more attributes, or a component prop to an expression.
<template>
<div id="app">
<img v-bind:src="imageUrl" :alt="imageAlt">
<a :href="link" :title="linkTitle">{{ linkText }}</a>
</div>
</template>
<script>
export default {
data() {
return {
imageUrl: 'https://vuejs.org/images/logo.png',
imageAlt: 'Vue Logo',
link: 'https://vuejs.org',
linkTitle: 'Visit Vue.js official website',
linkText: 'Vue.js'
}
}
}
</script>
Note the shorthand syntax :
which is equivalent to v-bind:
.
v-model
The v-model
directive creates a two-way binding on form inputs, textarea, and select elements. It automatically picks the correct way to update the element based on the input type.
<template>
<div id="app">
<input v-model="message" placeholder="Edit me">
<p>Message is: {{ message }}</p>
<textarea v-model="description" placeholder="Add a description"></textarea>
<p>Description is: {{ description }}</p>
<select v-model="selected">
<option disabled value="">Please select one</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<p>Selected: {{ selected }}</p>
</div>
</template>
<script>
export default {
data() {
return {
message: '',
description: '',
selected: ''
}
}
}
</script>
v-if, v-else-if, v-else
These directives are used for conditional rendering of elements:
<template>
<div id="app">
<div v-if="type === 'A'">
A is selected
</div>
<div v-else-if="type === 'B'">
B is selected
</div>
<div v-else-if="type === 'C'">
C is selected
</div>
<div v-else>
Nothing is selected
</div>
<button @click="changeType">Change Type</button>
</div>
</template>
<script>
export default {
data() {
return {
type: 'A'
}
},
methods: {
changeType() {
const types = ['A', 'B', 'C', 'D'];
const currentIndex = types.indexOf(this.type);
const nextIndex = (currentIndex + 1) % types.length;
this.type = types[nextIndex];
}
}
}
</script>
v-show
Similar to v-if
, but with different rendering behavior. v-show
will always render the element but toggle its CSS display
property.
<template>
<div id="app">
<h1 v-show="isVisible">Hello Vue!</h1>
<button @click="toggleVisibility">Toggle Visibility</button>
</div>
</template>
<script>
export default {
data() {
return {
isVisible: true
}
},
methods: {
toggleVisibility() {
this.isVisible = !this.isVisible;
}
}
}
</script>
v-for
The v-for
directive is used to render a list of items based on an array. It requires a special syntax in the form of item in items
:
<template>
<div id="app">
<ul>
<li v-for="(item, index) in items" :key="index">
{{ index }}: {{ item.message }}
</li>
</ul>
<h3>Object Properties</h3>
<ul>
<li v-for="(value, key, index) in object" :key="key">
{{ index }}. {{ key }}: {{ value }}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
items: [
{ message: 'Foo' },
{ message: 'Bar' }
],
object: {
title: 'Vue Directives',
author: 'Vue Team',
publishedAt: '2022-01-01'
}
}
}
}
</script>
Important: When using
v-for
, always provide a unique:key
attribute for each item. This helps Vue identify each node in the rendered list, enabling it to reuse and reorder existing elements efficiently.
v-on
The v-on
directive is used to attach event listeners to elements:
<template>
<div id="app">
<button v-on:click="counter += 1">Add 1</button>
<button @click="decrement">Subtract 1</button>
<p>The counter is: {{ counter }}</p>
<input @keyup.enter="submitForm" v-model="inputValue" placeholder="Press Enter to submit">
</div>
</template>
<script>
export default {
data() {
return {
counter: 0,
inputValue: ''
}
},
methods: {
decrement() {
if (this.counter > 0) {
this.counter -= 1;
}
},
submitForm() {
alert(`Submitting: ${this.inputValue}`);
this.inputValue = '';
}
}
}
</script>
The shorthand for v-on:
is @
. Notice how we can also use modifiers like .enter
to specify the event should only trigger when a specific key is pressed.
v-text and v-html
v-text
updates the element's text content, similar to using mustache interpolation:
<template>
<div id="app">
<span v-text="message"></span>
<!-- Same as -->
<span>{{ message }}</span>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello Vue!'
}
}
}
</script>
v-html
updates the element's inner HTML:
<template>
<div id="app">
<div v-html="rawHtml"></div>
</div>
</template>
<script>
export default {
data() {
return {
rawHtml: '<span style="color: red">This is red text</span>'
}
}
}
</script>
Warning: Using
v-html
can be dangerous because it can lead to XSS vulnerabilities if the content is user-generated. Only usev-html
on trusted content and never on user-provided content.
Custom Directives
Vue also allows you to define your own custom directives. Custom directives are useful when you need direct DOM access and manipulation.
Global Custom Directive
Here's how to register a custom directive globally:
// main.js
const app = createApp(App);
app.directive('focus', {
mounted(el) {
// Focus the element when the component is mounted
el.focus();
}
});
app.mount('#app');
Local Custom Directive
You can also register directives locally in a component:
<template>
<div id="app">
<input v-focus placeholder="This input will be focused on page load">
</div>
</template>
<script>
export default {
directives: {
focus: {
mounted(el) {
el.focus();
}
}
}
}
</script>
Directive Hooks
Custom directives have several hooks available:
created
: Called before the element's attributes or event listeners are applied.beforeMount
: Called before the element is inserted into the DOM.mounted
: Called when the element is inserted into the DOM.beforeUpdate
: Called before the containing component's VNode is updated.updated
: Called after the containing component's VNode and its children's VNodes have updated.beforeUnmount
: Called before a directive is unbound from an element.unmounted
: Called when a directive is unbound from an element.
Here's a more complex example using different hooks:
<template>
<div id="app">
<p v-highlight:background.bright="'yellow'">Highlighted text</p>
<button @click="toggleVisibility">Toggle Visibility</button>
<div v-if="isVisible" v-track-visibility>
This element is being tracked for visibility
</div>
</div>
</template>
<script>
export default {
data() {
return {
isVisible: true
}
},
methods: {
toggleVisibility() {
this.isVisible = !this.isVisible;
}
},
directives: {
highlight: {
mounted(el, binding) {
// binding.value is the value passed to the directive ('yellow')
// binding.arg is the argument ('background')
// binding.modifiers is an object containing modifiers ({bright: true})
const type = binding.arg || 'color';
const brightness = binding.modifiers.bright ? '255, 255, 255' : '0, 0, 0';
const opacity = binding.modifiers.bright ? '0.3' : '0.1';
if (type === 'background') {
el.style.backgroundColor = binding.value;
el.style.color = `rgba(${brightness}, ${opacity})`;
} else {
el.style.color = binding.value;
}
el.style.fontWeight = 'bold';
}
},
trackVisibility: {
mounted(el) {
console.log('Element is now visible');
},
unmounted() {
console.log('Element is no longer visible');
}
}
}
}
</script>
Real-World Examples
Let's look at some practical examples of how directives can be used in real-world applications:
Form Validation with Custom Directives
<template>
<div id="app">
<form @submit.prevent="submitForm">
<div class="form-group">
<label for="email">Email</label>
<input
id="email"
v-model="email"
v-validate="'required|email'"
:class="{'is-invalid': errors.has('email')}"
>
<span v-if="errors.has('email')" class="error-text">{{ errors.first('email') }}</span>
</div>
<button type="submit" :disabled="errors.any()">Submit</button>
</form>
</div>
</template>
<script>
export default {
data() {
return {
email: '',
errors: {
items: [],
has(field) {
return this.items.some(error => error.field === field);
},
first(field) {
const error = this.items.find(e => e.field === field);
return error ? error.message : '';
},
any() {
return this.items.length > 0;
}
}
};
},
directives: {
validate: {
mounted(el, binding, vnode) {
const field = el.id;
const rules = binding.value.split('|');
const component = vnode.ctx;
el.addEventListener('input', () => {
const value = el.value;
const errors = [];
if (rules.includes('required') && !value) {
errors.push({ field, message: 'This field is required' });
}
if (rules.includes('email') && value && !value.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
errors.push({ field, message: 'Please enter a valid email address' });
}
// Update the component's errors
component.errors.items = component.errors.items.filter(error => error.field !== field);
component.errors.items.push(...errors);
});
}
}
},
methods: {
submitForm() {
if (!this.errors.any()) {
alert('Form submitted successfully!');
}
}
}
}
</script>
<style>
.is-invalid {
border-color: red;
}
.error-text {
color: red;
font-size: 0.8em;
}
</style>
Animation with Custom Directive
<template>
<div id="app">
<button @click="show = !show">Toggle</button>
<div v-if="show" v-fade-in>
This content will fade in when shown
</div>
</div>
</template>
<script>
export default {
data() {
return {
show: false
}
},
directives: {
fadeIn: {
mounted(el) {
el.style.opacity = '0';
el.style.transition = 'opacity 0.5s ease-in-out';
// Wait for the next frame to apply the transition
setTimeout(() => {
el.style.opacity = '1';
}, 0);
}
}
}
}
</script>
Interactive Data Visualization Directive
<template>
<div id="app">
<div v-chart="chartData" style="width: 400px; height: 300px;"></div>
<button @click="updateData">Update Data</button>
</div>
</template>
<script>
export default {
data() {
return {
chartData: {
type: 'bar',
data: [12, 19, 8, 15, 22]
}
}
},
directives: {
chart: {
mounted(el, binding) {
// This is a simplified example. In a real app, you might use
// a library like Chart.js or D3.js to render the chart
renderChart(el, binding.value);
},
updated(el, binding) {
// Re-render the chart when data changes
renderChart(el, binding.value);
}
}
},
methods: {
updateData() {
// Generate new random data
this.chartData = {
type: 'bar',
data: Array.from({ length: 5 }, () => Math.floor(Math.random() * 30))
};
}
}
}
// Simple chart rendering function (for demonstration)
function renderChart(el, chartConfig) {
el.innerHTML = '';
const maxValue = Math.max(...chartConfig.data);
chartConfig.data.forEach(value => {
const bar = document.createElement('div');
bar.style.height = `${(value / maxValue) * 100}%`;
bar.style.width = `${100 / chartConfig.data.length - 2}%`;
bar.style.margin = '0 1%';
bar.style.backgroundColor = 'steelblue';
bar.style.display = 'inline-block';
bar.style.transition = 'all 0.3s ease';
bar.style.position = 'relative';
bar.style.bottom = '0';
const label = document.createElement('div');
label.textContent = value;
label.style.textAlign = 'center';
label.style.padding = '5px 0';
bar.appendChild(label);
el.appendChild(bar);
});
el.style.display = 'flex';
el.style.alignItems = 'flex-end';
el.style.justifyContent = 'center';
el.style.border = '1px solid #ccc';
el.style.padding = '10px';
}
</script>
Directive Flow Visualization
Let's visualize how directives are processed in Vue.js:
Summary
Directives are one of the most powerful features of Vue.js that allow you to create dynamic and interactive web applications. They provide a way to reactively update the DOM based on data changes.
In this tutorial, we covered:
-
The core built-in directives in Vue.js:
- v-bind: For attribute binding
- v-model: For two-way data binding
- v-if / v-else-if / v-else: For conditional rendering
- v-show: For toggling visibility
- v-for: For list rendering
- v-on: For event handling
- v-text and v-html: For content updates
-
How to create and use custom directives, both globally and locally.
-
Understanding directive hooks and their lifecycle.
-
Real-world applications of directives for form validation, animations, and data visualization.
Directives are a fundamental concept in Vue.js, and mastering them will greatly enhance your ability to create powerful Vue applications.
Exercises
To reinforce your learning, try these exercises:
- Create a custom
v-tooltip
directive that shows a tooltip when hovering over an element. - Build a
v-debounce
directive that can be used on input fields to debounce API calls. - Create a
v-lazy-load
directive for images that only loads them when they are visible in the viewport. - Implement a
v-permission
directive that conditionally shows/hides elements based on user permissions.
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)