Dynamically mount a single file component in Vue.js

14,339

Solution 1

Use Vue's Dynamic Components

You could use Dynamic Components to dynamically switch between components. You will need to bind the component definition object to the is attribute of the component element – Vue's documentation on this is pretty self explanatory. Below is also a brief example:

<template>
  <component :is="activeComponent"></component>
</template>
import componentA from 'component/a';
import componentB from 'component/b';

export default {
  components: {
    componentA,
    componentB,
  },

  data() {
    return {
      activeComponent: 'componentA',
    },
  },
};

You could directly bind the component definition object to the data property itself:

import componentA from 'component/a';
import componentB from 'component/b';

export default {
  data() {
    return {
      activeComponent: componentA,
    };
  },
};

To switch out components you can programmatically change the value of activeComponent.

Use a render function

A more powerful way of dynamically mounting components can be achieved using component render functions. To do this we must create our own version of Vue's component element – we'll call this the ElementProxy:

import componentA from 'component/a';
import componentB from 'component/b';

export default {
  components: {
    componentA,
    componentB,
  },

  props: {
    type: {
      type: String,
      required: true,
    },
    props: {
      type: Object,
      default: () => ({}),
    },
  },

  render(createElement) {
    const { props: attrs } = this;
    return createElement(element, { attrs });
  },
};

You can now use the ElementProxy to proxy elements. The additional benefit of this is that you can pass props in as an object which will solve the problem of passing props to dynamic components with differing models.

<template>
  <element-proxy :type="activeComponent" :props="props"></element-proxy>
</template>
import ElementProxy from 'components/elementProxy';

export default {
  components: {
    ElementProxy,
  },

  data() {
    return {
      activeComponent: 'componentA',
      props: { ... },
    };
  },
};

Further reading

Solution 2

Yes, you need dynamic components:

<template>
  <div>
    <component v-bind:is="currentView">
    <!-- component changes when vm.currentView changes! -->
    </component>
  </div>
</template>

<script>
import A from './A.vue';
import B from './B.vue';

export default {
  data: {
    currentView: 'A'
  },
  components: {
    A,
    B,
  }
})

then

function getComponent(){
  if (..)
    this.currentView = 'A';
  else if (..)
    this.currentView = 'B'
  ...
}

You can also bind components directly, according to manual:

https://vuejs.org/v2/guide/components.html#Dynamic-Components

Share:
14,339

Related videos on Youtube

Michael_S_
Author by

Michael_S_

Updated on June 04, 2022

Comments

  • Michael_S_
    Michael_S_ almost 2 years

    I have a single file component Main.Vue.

    I also have three other single file components A.vue, B.vue and C.vue.

    I want to be able to show inside Main.Vue a different component each time. What I did was this:

    <template>
    <div>
    <a v-if="isAVisible" ></a>
    <b v-if="isBVisible" ></a>
    </div>
    </template>
    
    <script>
    import A from './A.vue';
    import B from './B.vue';
    ...
    

    This works but not exactly what I wanted. I wanted a different file Factory.js, which does the importing of all the components A,B,C,.. And has functions that return my component, which I can use somehow in Main.vue. Here's what I tried Factory.js to look like:

    import A from './A.vue';
    import B from './B.vue';
    function getComponent(){
      if (..)
        return new A();
      else if (..)
        return new B();
      ...
    }
    

    This didn't work at all. I want the factory file approach because:

    1) I want to split it to different factory files

    2) I want to "Attach" data to each component. So I'll have an object that contains the function returning the actual component + some additional data like "name"

    Any ideas how to do this?

  • Michael_S_
    Michael_S_ almost 7 years
    Thanks! I used the ElementProxy and it made the code much nicer