Dynamic components using VueJS
Tags: vue
Published: 2019-07-26 08:08:37


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](https://vuejs.org/v2/guide/), 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](https://vuejs.org/v2/guide/components-dynamic-async.html) 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](https://codepen.io/pikax/pen/aeNKop)

```javascript
// 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>`
});
```

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.

```javascript
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>
```

This will pass the prop name to every component. To solve this we can have a computed property and [bind](https://vuejs.org/v2/api/#v-bind) it to the component.

```javascript
// 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>`
});
```

## 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](https://codepen.io/pikax/pen/NQNBPR)

```javascript
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>
`
});
```

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]

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

### 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](https://vuejs.org/v2/guide/components-dynamic-async.html#Async-Components), (Dynamic)[https://vuejs.org/v2/guide/components-dynamic-async.html] components and Webpack.

```javascript
// 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>
        `
});
```


# 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 ``