Vue.js Dynamic Components

Dynamic components let one spot on the page display different components at different times. A tab panel is the classic case: a click on "Profile" shows the profile panel, and a click on "Orders" swaps in the orders panel. Vue handles the swap with a single built-in element named <component> and a special attribute named is.

Imagine a picture frame on a wall. The frame stays fixed while you slide different photos into it. The <component> element is the frame, and each component is a photo.

The component Element and the is Attribute

The is attribute tells Vue which component to draw. Bind it to a data property and the displayed component changes whenever that property changes.

<div id="app">
  <button @click="current = 'home-page'">Home</button>
  <button @click="current = 'about-page'">About</button>
  <button @click="current = 'contact-page'">Contact</button>

  <component :is="current"></component>
</div>

<script>
  const app = Vue.createApp({
    data() {
      return { current: "home-page" };
    }
  });

  app.component("home-page",    { template: "<p>Welcome home.</p>" });
  app.component("about-page",   { template: "<p>About our team.</p>" });
  app.component("contact-page", { template: "<p>Send us a message.</p>" });

  app.mount("#app");
</script>

The variable current holds the registered name of a component. A click on a button changes the name, and Vue replaces the old component with the new one.

Diagram: Swapping Components

current = "home-page"
  +---------------------------+
  | <component :is="current"> |  -->  shows HomePage
  +---------------------------+

Click "About" --> current = "about-page"
  +---------------------------+
  | <component :is="current"> |  -->  shows AboutPage
  +---------------------------+
(HomePage is destroyed, AboutPage is created)

Dynamic Components in script setup

Components written with <script setup> import component objects directly. Bind is to the imported object instead of a text name. Use shallowRef so Vue does not wrap the component object in a deep reactive proxy.

<script setup>
import { shallowRef } from "vue";
import Home from "./Home.vue";
import About from "./About.vue";

const current = shallowRef(Home);
</script>

<template>
  <button @click="current = Home">Home</button>
  <button @click="current = About">About</button>
  <component :is="current" />
</template>

Pass Props and Listen to Events

Props and event listeners written on the <component> element travel to whichever component is active. Every component in the switch list should accept the same props, or ignore the ones it does not need.

<component :is="current" :user="userName" @saved="showMessage"></component>

Build a Tab Interface

A tab list works well with an array of tab names. A loop draws the buttons, and the active name feeds the is attribute.

data() {
  return {
    tabs: ["tab-profile", "tab-orders", "tab-settings"],
    active: "tab-profile"
  };
}

<button v-for="t in tabs" :key="t" @click="active = t">
  {{ t }}
</button>
<component :is="active"></component>

Diagram: Tab Data Flow

tabs   = [profile, orders, settings]
active = "tab-orders"
            |
            v
   v-for draws 3 buttons
   click on a button --> active changes
            |
            v
   <component :is="active"> redraws with the chosen tab

State Is Lost on Every Swap

Vue destroys the old component when a new one takes its place. Text typed into a form on the old tab disappears. Counters reset to their starting values. Plan for this behavior: store anything that must survive in the parent component, in a shared store, or in the browser's local storage.

Diagram: Lost State

Orders tab: user types "Pizza" in a search box
Click "Profile"  --> Orders component destroyed ("Pizza" is gone)
Click "Orders"   --> new Orders component created (empty box)

Choose Between v-if and component is

  • Use v-if and v-else for two or three fixed choices that you can read at a glance.
  • Use <component :is> for long lists of choices, or when the component name comes from data such as a server response.

Summary

The <component> element renders any component named by its is attribute. A data property controls the choice, which makes tabs, wizards, and widget dashboards simple to build. Props and events pass through to the active component. Each swap destroys the previous component, so shared data belongs outside the swapped components.