vue

Dynamic components using VueJS

pikax

Carlos Rodrigues

Posted on July 26, 2019

Dynamic components using VueJS

First things first, if you're just starting with VueJS this might be a bit too advanced for you, I would strongly recommend read documentation on VueJS, especially components.

This is my first guide, criticism is welcome :)

Preface

Swapping components based on user inputs or even just have a form being set by a json file, is really usefull method to maintain your sanity level low throughout a big project, since usually there's hundreds components/forms/pages/etc, so making a change in code it might cause a ripple effect and break something elsewhere.

Since VueJS will handle all the dirty details of handling DOM, we can focus on solve business problems.

I will cover loading components by name, creating on-the-fly and async components.

Component "magic"

Doing dynamic wouldn't be as easy without <component v-bind:is="dynComponent"></component> check Dynamic & Async Components for more detailed information.
Basically the components will render a component, async function or by component name.

Loading components by name

Using <component/> will allow you to access global and also local components by name.

codepen

// add some different components globaly
Vue.component("test-comp0", {
  template: `<p>comp0</p>`
});

Vue.component("test-comp1", {
  template: `<p>comp1</p>`
});

Vue.component("test-comp2", {
  template: `<p>comp2</p>`
});

// sample app
new Vue({
  el: "#app",

  components: {
    // add a local component
    // check https://vuejs.org/v2/guide/components-registration.html#Component-Names
    TestComp3: {
      template: `<p>comp3 locally registered component</p>`
    }
    // you can also add some components from other files, using ES6 import or required.
  },

  data() {
    return {
      componentIndex: 0
    };
  },

  computed: {
    componentName() {
      return "test-comp" + this.componentIndex;
    }
  },

  template: `
      <div>
        Component: {{componentIndex}} <button @click="componentIndex=(++componentIndex)%4">change</button>
        <component :is="componentName"></component>
      </div>`
});
Enter fullscreen mode Exit fullscreen mode

Cycle between components is useful, but in the real world you would pass some props to it.
To add props, lets change the component test-comp0 and to the app template.

Vue.component("test-comp0", {
  props: ["name"], // please use the object props for production
  template: `<p>Hello {{name}}</p>`
});

...

// add name prop
<component :is="componentName" name="pikax"></component>
Enter fullscreen mode Exit fullscreen mode

This will pass the prop name to every component. To solve this we can have a computed property and bind it to the component.

// app becomes

new Vue({
  el: "#app",

  components: {
    // add a local component
    // check https://vuejs.org/v2/guide/components-registration.html#Component-Names
    TestComp3: {
      template: `<p>comp3 locally registered component</p>`
    }
    // you can also add some components from other files, using ES6 import or required.
  },

  data() {
    return {
      componentIndex: 0,
      name: "pikax"
    };
  },

  computed: {
    componentName() {
      return "test-comp" + this.componentIndex;
    },
    componentProps() {
      if (this.componentIndex == 0) {
        return {
          name: this.name
        };
      }
      return {}; // return empty object
    }
  },

  template: `
      <div>
        Component: {{componentIndex}} <button @click="componentIndex=(++componentIndex)%4">change</button>
        <component :is="componentName" v-bind="componentProps"></component>
      </div>`
});
Enter fullscreen mode Exit fullscreen mode

On-the-fly components

On-the-fly components are components we just generate as we need using javascript, this shows how powerful the <component></component> is, some use case scenarios would be building widgets.

We can generate and test components based on the user input.

NOTE: be really careful with this, this can allow attackers to attack your application, please ensure the source is trusted!

codepen

new Vue({
  el: "#app",

  data() {
    return {
      componentDefinition: `{ template: "<div>Hello</div>" }`
    };
  },

  computed: {
    myComponent() {
      return eval(`(${this.componentDefinition})`);
    }
  },

  template: `<div>
    <p>Change me</p>
    <textarea v-model="componentDefinition" rows="4" cols="50"></textarea>
    <component v-if="myComponent" :is="myComponent"></component>
</div>
`
});
Enter fullscreen mode Exit fullscreen mode

You can see as you change the textarea, the component should render straight away.
I don't recommend using this, but I think is a good example on how powerful <component></component> is.

Importing async Components

This is for me the most usefull use case of the component. I greatly recommend reading the (official guide)[https://vuejs.org/v2/guide/components-dynamic-async.html#Async-Components]

Vue.component(
  "async-webpack-example",
  // The `import` function returns a Promise.
  () => import("./my-async-component")
);
Enter fullscreen mode Exit fullscreen mode

Real world problem

In my last project we had a problem of we wanted to collect user information, but the fields would change depending on the journey, some journey would required email, others email and phone.

The solution was to get the journey definition in a JSON file, each time the user would start an journey we would load that file and load the fields.
The strategy was to use names to load the component dynamically loading the components by name, but we ended up loading all the possible editors in Vue.Component, this worked... but loading them at the start up means the startup time and app size were much bigger than needed.

Solution

Using a mix of Async, (Dynamic)[https://vuejs.org/v2/guide/components-dynamic-async.html] components and Webpack.

// returning equivalent of webpack : import(name)
const getComponent = async path => {
  /* I recomend having an switch with the possible components you will load, this
   *   will allow you only load specific components.
   */
  if (path == 1) {
    return async () => {
      template: `<p>component 0</p>`;
    };
  } else {
    return async () => {
      template: `<p>${path}</p>`;
    };
  }
};

Vue.component("component-fallback", {
  template: `<div>This is not the component you're looking for</div>`
});

new Vue({
  el: "#app",
  data() {
    return {
      componentIndex: 0,
      component: "component-fallback"
    };
  },

  methods: {
    changeComponent() {
      const newIndex = ++this.componentIndex;
      this.loadComponent(newIndex);
    },

    // returns the component
    loadComponent(name) {
      const componentFunc = getComponent(name)
        .then(x => {
          this.component = x;
        })
        .catch(e => {
          this.component = "component-fallback";
        });
    }
  },

  template: `
        <div>
            Component: {{componentIndex}} <button @click="changeComponent">change</button>
            <component :is="component"></component>
        </div>
        `
});
Enter fullscreen mode Exit fullscreen mode

End

Hope my first article is useful for you, I find fascinating how powerful and flexible <component></component> is.

If you have any more use cases for <component></component> let me know on the comments.

This story was first publish at medium.com

💖 💪 🙅 🚩
pikax
Carlos Rodrigues

Posted on July 26, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related