How to test whether component with specific name is registered in Composition API (Vue 3)

Viewed 1572

I have a template loading dynamic components via their name:

<template>
      <div>
        <div>
          <div>
            <component
              :is="getFormRenderer"
            ></component>
          </div>
        </div>
      </div>
</template>

getFormRendererreturns a string (it comes from an API) that identifies the component by name.

All possible sub-components (in this example only telegram_send_message and time_time are imported and registered:


<script>
import { useStore } from "vuex";
import { computed } from "vue";

import exampleComponentOne from "@/components/forms/exampleComponentOne.vue";
import exampleComponentTwo from "@/components/forms/exampleComponentTwo.vue";
import defaultComponentTwo from "@/components/forms/defaultComponent.vue";

export default {
  name: "ActionEditor",
  setup() {
    const store = useStore();

    const getFormRenderer = computed(() => {
      return (
        store.state.level.actionSelected.plugin
      );
    });
    return {
      getFormRenderer,
    };
  },
  components: {
    exampleComponentOne,
    exampleComponentTwo,
    defaultComponent
  },
};
</script>

Now I want to modify the dynamic <component> in a way that it defaults to defaultComponent.vue if the getFormRenderer returns a component name that does not exist.

But I can't find any way to realize this. I thought of using this.hasOwnProperty() but inside of the setup() this does is undefined.

Is there a standard way to do that?

2 Answers

A simple way to check whether a component exists in setup() is to use resolveComponent() or resolveDynamicComponent(), which looks up a component by name. Note the docs for resolveDynamicComponent() state that it raises a warning for nonexistent components, but it's actually resolveComponent() that does that (as of v3.0.9).

Both functions return the given component name if the component is not found, so you could determine whether the component exists by verifying the return type is not a string:

import { computed, resolveDynamicComponent } from 'vue'

export default {
  setup() {
    const isComponent = name => typeof resolveDynamicComponent(name) !== 'string'

    const store = useStore();
    const getFormRenderer = computed(() => 
      isComponent(store.state.level.actionSelected.plugin)
        ? store.state.level.actionSelected.plugin
        : 'DefaultComponent'
    );

    return {
      getFormRenderer
    }
  },
  components: {
    //...
  },
}

demo

To test whether component with specific name is registered globally in the app you can use app.component() function passing only first argument (component name)

To do that in any component in your app, you can use a (rather sparsely documented) function getCurrentInstance - this function can be called only inside setup or hooks and you can use it's return value to test if component is registered

  1. For global components use getCurrentInstance().appContext.app.component() function (where appContext.app property represents main app object created with createApp())

  2. For locally registered components use getCurrentInstance().components['name']

See the example bellow for global components (note that example is using Vue from CDN so it is using global Vue object. In normal development you use imports instead - for example import { getCurrentInstance } from 'vue')

const app = Vue.createApp({})

app.component('mainComponent', {
  template: `
    <component :is="getComp1OrDefault"></component>
    <component :is="getComp2OrDefault"></component>
  `,
  setup() {  
    const vm =  Vue.getCurrentInstance()
    //console.log(vm) 
    const getComp1OrDefault = Vue.computed(() => {
      return vm.appContext.app.component('comp1') ? 'comp1' : `defaultComp`
    });
    
    const getComp2OrDefault = Vue.computed(() => {
      return vm.appContext.app.component('comp2') ? 'comp2' : `defaultComp`
    });
    
    return {
      getComp1OrDefault,
      getComp2OrDefault
    };
  }
})

app.component('comp1', {
  template: `<div>Comp1</div>`
})

app.component('defaultComp', {
  template: `<div>defaultComp</div>`
})

app.mount("#app")
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.7/vue.global.js" integrity="sha512-+i5dAv2T8IUOP7oRl2iqlAErpjtBOkNtREnW/Te+4VgQ52h4tAY5biFFQJmF03jVDWU4R7l47BwV8H6qQ+/MfA==" crossorigin="anonymous"></script>

<div id="app">
  <main-component />
</div>

Related