Building Flexible Vue 2 Applications with Dynamic Components, Slots, and Custom Directives
Dynamic Components in Vue 2
Vue provides a <component> element specifically designed for rendering components dynamically. When switching between components, the default behavior destroys inactive components, meaning any state or data is lost when a component is toggled off and back on.
Preserving State with keep-alive
The <keep-alive> wrapper solves this problem by caching component instances instead of destroying them. Components wrapped in <keep-alive> maintain their state across switches.
<keep-alive include="Header,Footer">
<component :is="currentView"></component>
</keep-alive>
data() {
return {
currentView: 'Header'
}
}
The include attribute accepts a comma-separated list of component names to cache. The exclude attribute works inversely—specifying which components should not be cached.
Lifecycle Hooks with keep-alive
When using <keep-alive>, two additional lifecycle hooks become available:
deactivated— triggered when a component is removed from the active cacheactivated— triggered when a component enters the active cache
These hooks are useful for scenarios like pausing animations when a tab is hidden or resuming data fetching when a user returns to a view.
Understanding Slots
Slots provide a mechanism for component authors to define placeholders where consumers can inject their own content. This enables flexible component composition without the component needing to know anything about the content being passed in.
Basic Slot Usage
When a component includes a <slot> element, any content placed inside the component tags gets rendered at that location:
<!-- Definition -->
<template>
<div class="card">
<h2>Card Title</h2>
<slot></slot>
</div>
</template>
<!-- Usage -->
<Card>
<p>This paragraph appears in the slot</p>
</Card>
If no content is provided, the slot renders nothing. If multiple slots exist with no names, all content goes to the unnamed slot.
Named Slots
Named slots allow multiple content areas within a single component. Each slot requires a unique name attribute:
<!-- Layout.vue -->
<template>
<div class="layout">
<header>
<slot name="header"></slot>
</header>
<main>
<slot name="body"></slot>
</main>
<aside>
<slot name="sidebar"></slot>
</aside>
</div>
</template>
When using a component with named slots, wrap content in <template> tags and use the v-slot directive with the slot name:
<Layout>
<template v-slot:header>
<h1>Site Title</h1>
</template>
<template #body>
<p>Main content goes here</p>
</template>
<template v-slot:sidebar>
<nav>Navigation links</nav>
</template>
</Layout>
The # shorthand is equivalent to v-slot: and works with any slot name.
Scoped Slots
Scoped slots allow components to expose data to the parent component that consumes the slot. The child component passes data through slot props, and the parent decides how to render that data.
<!-- DataTable.vue -->
<template>
<table>
<thead>
<tr><th>Name</th><th>Email</th></tr>
</thead>
<tbody>
<tr v-for="row in tableData" :key="row.id">
<slot name="row" :data="row"></slot>
</tr>
</tbody>
</table>
</template>
<script>
export default {
data() {
return {
tableData: [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' }
]
}
}
}
</script>
The parent component receives this data and can access it through the slot scope:
<DataTable>
<template #row="{ data }">
<td>{{ data.name }}</td>
<td>{{ data.email }}</td>
</template>
</DataTable>
Destructuring slot props directly in the template ({ data }) provides cleaner access to individual properties compared to using a single scope object.
Custom Directives
While Vue includes many built-in directives like v-model, v-if, and v-for, custom directives extend the framework to manipulate the DOM directly. Directives are particularly useful for low-level DOM access that components cannot handle.
Local Directives
Local directives are defined within a single component's directives option:
<template>
<div>
<input v-focus placeholder="Auto-focus input" />
</div>
</template>
<script>
export default {
directives: {
focus: {
inserted(el) {
el.focus()
}
}
}
}
</script>
The directive lifecycle includes several hook functions:
bind— called once when the directive first binds to the elementinserted— called when the element is inseretd into the parent DOM nodeupdate— called when the component updates, before children are updatedcomponentUpdated— called after the component and children have updatedunbind— called once when the directive is unbound from the element
Passing Values to Directives
Directives can receive dynamic values using binding syntax:
<p v-highlight="'yellow'">Highlighted text</p>
directives: {
highlight: {
bind(el, binding) {
el.style.backgroundColor = binding.value
},
update(el, binding) {
el.style.backgroundColor = binding.value
}
}
}
The binding object provides several properties:
value— the current value passed to the directiveoldValue— the previous value (available in update and componentUpdated)expression— the attribute value as a stringarg— the argument passed to the directive (e.g.,colorinv-background:color)modifiers— an object containing any modifiers
When both bind and update perform identical operations, they can be combined into a single function shorthand:
directives: {
highlight(el, binding) {
el.style.backgroundColor = binding.value
}
}
Global Directives
Global directives are registered on the Vue constructor and available across all components. Register these in the application entry point:
// main.js
Vue.directive('click-outside', {
bind(el, binding) {
el._clickOutside = (event) => {
if (!el.contains(event.target)) {
binding.value(event)
}
}
document.addEventListener('click', el._clickOutside)
},
unbind(el) {
document.removeEventListener('click', el._clickOutside)
}
})
This example creates a v-click-outside directive that triggers a callback when the user clicks outside the element—commonly used for dropdown menus and modals.
Code Quality Standards
When initializing a project with the Vue CLI, enabling lint-on-save adds ESLint configuration files and integrates code checking into the development workflow. This catches style violations and potential errors during development rather than during production.
Recommended Extensions
ESLint — analyzes code for patterns that might indicate problems, with extensive configuration options for different coding styles.
Prettier — handles automatic code formatting, ensuring consistent style across the codebase. Configure it to work alongside ESLint by disabling conflicting rules:
// .prettierrc
{
"semi": false,
"singleQuote": true,
"tabWidth": 4,
"trailingComma": "none"
}
Vetur — provides Vue-specific support including syntax highlighting, IntelliSense, and template formatting for .vue files.
Consistent code standards reduce review overhead, make codebase navigation easier, and help prevent bugs through automated enforcement of conventions.