Vue Instance and Directives

Module 14: JavaScript Frontend Frameworks - Vue & Angular

Introduction to the Vue Instance

Every Vue application starts with creating a Vue instance - the heart of a Vue application. In Vue 3, we create this instance using the createApp() function, which serves as the entry point and connects the application to a DOM element.

// Vue 3 App Creation
const app = Vue.createApp({
  // root component options
})

// Mount the app to a DOM element
app.mount('#app')

This creates a connection between your Vue code and the HTML element with id="app", establishing the boundary of your Vue application. Everything inside this element becomes reactive and managed by Vue.

Real-world analogy: Think of the Vue instance as the brain of your application. Just as the brain coordinates all body systems, the Vue instance orchestrates all components, directives, and reactivity of your application.

Vue 2 vs Vue 3 App Creation

If you encounter Vue 2 code, you'll notice a different syntax for creating instances:

Vue 2 (older)

// Creating a Vue instance in Vue 2
new Vue({
  // options here
}).$mount('#app')

Vue 3 (current)

// Creating a Vue app in Vue 3
Vue.createApp({
  // options here
}).mount('#app')

Vue 3's approach provides better separation of concerns and more flexibility for advanced use cases, particularly in larger applications with plugins, custom components, and global configurations.

App Configuration and the Root Component

When creating a Vue application, we pass a configuration object to createApp() - this object defines the root component of our application. The object contains various properties:

classDiagram class VueAppOptions { data() methods computed watch components props mounted() updated() unmounted() ...other lifecycle hooks } VueAppOptions --> VueApp: Creates VueApp --> DOM: Controls

Core Options in the Root Component

Vue.createApp({
  // Reactive state data
  data() {
    return {
      count: 0,
      message: 'Hello Vue!'
    }
  },
  
  // Methods that can be called from templates
  methods: {
    increment() {
      this.count++
    }
  },
  
  // Derived data properties
  computed: {
    doubleCount() {
      return this.count * 2
    }
  },
  
  // Watch for changes and react
  watch: {
    count(newValue, oldValue) {
      console.log(`Count changed from ${oldValue} to ${newValue}`)
    }
  },
  
  // Lifecycle hook - when component is mounted to DOM
  mounted() {
    console.log('Application loaded and mounted!')
  }
}).mount('#app')

Vue Directives Overview

Directives are special attributes in Vue templates that apply reactive behavior to the rendered DOM. They are recognizable by the v- prefix and are one of Vue's most powerful features.

Real-world analogy: Directives are like "smart instructions" attached to elements in your HTML. If regular HTML attributes are like static signs (e.g., "EXIT" signs that never change), directives are like digital signs that can change their displayed information based on conditions or events.

flowchart LR A[Vue Template] --> B[Vue Compiler] B --> C[Render Function] C --> D[Virtual DOM] D --> E[Real DOM] F[v-directives] -.-> B style A fill:#42b883,color:white style B fill:#42b883,color:white style C fill:#42b883,color:white style D fill:#42b883,color:white style E fill:#dddddd,color:black style F fill:#35495e,color:white

Core Vue Directives

Vue provides several built-in directives, each with a specific purpose:

v-text and Mustache Syntax

The most basic form of data binding in Vue is text interpolation using mustache syntax or the v-text directive:

<!-- Using mustache syntax (preferred for text) -->
<p>{{ message }}</p>

<!-- Equivalent using v-text directive -->
<p v-text="message"></p>

Real-world use case: Displaying dynamic user information like names, account balances, or product details that come from your application data.

v-html

For rendering HTML content rather than plain text:

<div v-html="rawHtml"></div>

Security Warning: Never use v-html with user-provided content as it can lead to XSS vulnerabilities. Only use it with trusted content.

Real-world use case: Rendering formatted content from a CMS where the HTML has been sanitized server-side, like a blog post with formatting.

v-bind

Used to dynamically bind attributes to an element based on your data:

<!-- Full syntax -->
<img v-bind:src="imageUrl" v-bind:alt="imageDescription">

<!-- Shorthand (preferred) -->
<img :src="imageUrl" :alt="imageDescription">

Real-world use case: Dynamically changing an image source, toggling CSS classes, or updating a link URL based on application state.

Dynamic Class Binding (Common Use Case)

<!-- Binding a single class conditionally -->
<div :class="{ active: isActive }"></div>

<!-- Binding multiple classes conditionally -->
<div :class="{ active: isActive, 'text-danger': hasError }"></div>

<!-- Combining with static classes -->
<div class="btn" :class="{ 'btn-primary': isPrimary, 'btn-danger': isDanger }"></div>

v-on

Used to listen to DOM events and trigger JavaScript when they're triggered:

<!-- Full syntax -->
<button v-on:click="increment">Increment</button>

<!-- Shorthand (preferred) -->
<button @click="increment">Increment</button>

<!-- With inline handler -->
<button @click="count++">Increment</button>

Real-world use case: Handling form submissions, button clicks, user interactions, or responding to custom events from child components.

Event Modifiers

Vue provides modifiers for common event handling tasks:

<!-- Stop propagation -->
<button @click.stop="handleClick">Click</button>

<!-- Prevent default -->
<form @submit.prevent="submitForm">...</form>

<!-- Only trigger if event.key is Enter -->
<input @keyup.enter="submit">

Conditional & List Rendering Directives

v-if, v-else-if, v-else

These directives conditionally render elements based on the truthiness of an expression:

<div v-if="type === 'A'">Type A</div>
<div v-else-if="type === 'B'">Type B</div>
<div v-else>Not A or B</div>

Real-world use case: Showing different UI components based on user roles, toggle between views, or displaying error messages conditionally.

v-show

Similar to v-if but toggles the element's CSS display property instead of adding/removing it from the DOM:

<div v-show="isVisible">This toggles visibility using CSS display</div>

When to use v-show vs v-if:

v-for

Used to render lists of items based on an array:

<!-- Basic array iteration -->
<ul>
  <li v-for="item in items" :key="item.id">
    {{ item.name }}
  </li>
</ul>

<!-- With index -->
<ul>
  <li v-for="(item, index) in items" :key="item.id">
    {{ index }}: {{ item.name }}
  </li>
</ul>

<!-- Object properties iteration -->
<ul>
  <li v-for="(value, key) in object" :key="key">
    {{ key }}: {{ value }}
  </li>
</ul>

Important: Always use the :key directive with v-for to give Vue a way to track each node's identity, making DOM updates more efficient.

Real-world use case: Rendering lists of products, user comments, to-do items, or table rows from your application data.

Two-Way Binding: v-model

The v-model directive creates two-way data binding between form inputs and the application state, combining v-bind and v-on in a single directive:

<!-- Basic text input -->
<input v-model="message">
<p>Message: {{ message }}</p>

<!-- Checkbox -->
<input type="checkbox" v-model="checked">
<p>Checked: {{ checked }}</p>

<!-- Radio buttons -->
<input type="radio" v-model="picked" value="One">
<input type="radio" v-model="picked" value="Two">
<p>Picked: {{ picked }}</p>

<!-- Select dropdown -->
<select v-model="selected">
  <option value="">Please select one</option>
  <option value="A">Option A</option>
  <option value="B">Option B</option>
</select>
<p>Selected: {{ selected }}</p>

Under the hood: v-model is essentially a shorthand for:

<!-- This: -->
<input v-model="message">

<!-- Is roughly equivalent to this: -->
<input 
  :value="message" 
  @input="message = $event.target.value">

Real-world use case: Building forms for user input, search interfaces, filtering controls, or any UI that requires collecting user input.

v-model Modifiers

Vue provides special modifiers for v-model to handle common form input transformations:

<!-- Trim whitespace from input -->
<input v-model.trim="message">

<!-- Cast input value to number -->
<input v-model.number="age" type="number">

<!-- Only update after change event, not on every input -->
<input v-model.lazy="message">

Special Directives

v-once

Renders the element and component only once, then becomes static content:

<span v-once>This will never change: {{ msg }}</span>

Real-world use case: Optimizing performance for content that won't change, like fixed headings or static configuration values.

v-pre

Skips compilation for this element and all its children:

<span v-pre>{{ This will be displayed as-is }}</span>

Real-world use case: Displaying raw mustache syntax or temporarily disabling compilation for a section during debugging.

v-cloak

Used to hide un-compiled mustache bindings until the Vue instance is ready:

<!-- In your CSS -->
[v-cloak] { 
  display: none; 
}

<!-- In your HTML -->
<div v-cloak>
  {{ message }}
</div>

Real-world use case: Preventing "template flashing" in applications where there might be a noticeable delay before the Vue application loads completely.

Custom Directives

Vue allows creating custom directives for specialized DOM manipulations:

// Define a global custom directive
const app = Vue.createApp({})

// Register a global custom directive 'v-focus'
app.directive('focus', {
  // When the bound element is mounted into the DOM
  mounted(el) {
    // Focus the element
    el.focus()
  }
})

// Usage in template
<input v-focus>

Directive Hook Functions: Custom directives can implement various hooks:

Real-world use case: Creating reusable behaviors like auto-focusing inputs, implementing scroll or lazy-loading behaviors, or handling custom tooltips.

Complete Instance Example

Let's put it all together with a practical example of a Vue instance using various directives:

<!DOCTYPE html>
<html>
<head>
  <title>Vue Instance & Directives Demo</title>
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <style>
    .completed { text-decoration: line-through; color: gray; }
    [v-cloak] { display: none; }
  </style>
</head>
<body>
  <div id="app" v-cloak>
    <h1>{{ title }}</h1>
    
    <!-- Input with v-model -->
    <input v-model.trim="newTask" placeholder="Add a task" @keyup.enter="addTask">
    <button @click="addTask" :disabled="!newTask">Add</button>
    
    <!-- Conditional rendering -->
    <p v-if="tasks.length === 0">No tasks yet. Add one!</p>
    
    <!-- List rendering with multiple directives -->
    <ul v-else>
      <li v-for="(task, index) in tasks" :key="task.id" 
          :class="{ completed: task.completed }">
        <input type="checkbox" v-model="task.completed">
        <span v-if="!task.editing">{{ task.text }}</span>
        <input v-else v-model="task.text" @blur="task.editing = false" v-focus>
        
        <!-- Event handling -->
        <button @click="task.editing = !task.editing">Edit</button>
        <button @click="removeTask(index)">Delete</button>
      </li>
    </ul>
    
    <!-- Computed property usage -->
    <p>Remaining: {{ remainingTasks }}</p>
  </div>

  <script>
    // Register a custom focus directive
    const app = Vue.createApp({
      data() {
        return {
          title: 'Task Manager',
          newTask: '',
          tasks: [
            { id: 1, text: 'Learn Vue Basics', completed: false, editing: false },
            { id: 2, text: 'Build a Todo App', completed: false, editing: false }
          ],
          nextId: 3
        }
      },
      
      computed: {
        remainingTasks() {
          return this.tasks.filter(task => !task.completed).length
        }
      },
      
      methods: {
        addTask() {
          if (this.newTask.trim()) {
            this.tasks.push({ 
              id: this.nextId++, 
              text: this.newTask, 
              completed: false,
              editing: false
            })
            this.newTask = ''
          }
        },
        
        removeTask(index) {
          this.tasks.splice(index, 1)
        }
      }
    })
    
    // Register a custom directive
    app.directive('focus', {
      mounted(el) {
        el.focus()
      }
    })
    
    // Mount the app
    app.mount('#app')
  </script>
</body>
</html>

This example demonstrates:

Activities for Practice

Exercise 1: Interactive Name Badge

Create a simple Vue application that displays a name badge. Include:

  • An input field where users can type their name (use v-model)
  • A color picker for the badge background (use v-model with a select dropdown)
  • A live preview of the badge showing the entered name and selected color (use v-bind for styling)
  • A button to toggle the badge visibility (use v-show or v-if)

Exercise 2: Shopping List Builder

Create a shopping list application that allows users to:

  • Add items to a list (use v-model and v-on)
  • Display the list of items (v-for with :key)
  • Mark items as purchased (use v-bind for styling and v-model with checkboxes)
  • Delete items from the list (v-on with click event)
  • Show a message when the list is empty (v-if/v-else)

Exercise 3: Custom Directive Creation

Create a Vue application with a custom directive. For example:

  • Create a v-highlight directive that changes the background color of an element when hovered
  • Create a v-format-currency directive that formats a number as currency
  • Create a v-autoresize directive for textareas that automatically resizes based on content

Apply your custom directive to appropriate elements in a simple Vue application.

Additional Resources