Vue 3: component `:is` in for loop fails

Viewed 581

I'm trying to loop over a list of component described by strings (I get the name of the component from another , like const componentTreeName = ["CompA", "CompA"].

My code is a simple as:

<script setup>
    import CompA from './CompA.vue'
    import { ref } from 'vue'

    // I do NOT want to use [CompA, CompA] because my inputs are strings
    const componentTreeName = ["CompA", "CompA"]
</script>

<template>
  <h1>Demo</h1>
  <template v-for="compName in componentTreeName">
    <component :is="compName"></component>
  </template>
</template>

Demo here

EDIT

I tried this with not much success.

2 Answers

Use resolveComponent() on the component name to look up the global component by name:

<script setup>
import { resolveComponent, markRaw } from 'vue'
const myGlobalComp = markRaw(resolveComponent('my-global-component'))
</script>

<template>
  <component :is="myGlobalComp" />
<template>

demo 1

If you have a mix of locally and globally registered components, you can use a lookup for local components, and fall back to resolveComponent() for globals:

<script setup>
import LocalComponentA from '@/components/LocalComponentA.vue'
import LocalComponentB from '@/components/LocalComponentB.vue'
import { resolveComponent, markRaw } from 'vue'

const localComponents = {
  LocalComponentA,
  LocalComponentB,
}

const lookupComponent = name => {
  const c = localComponents[name] ?? resolveComponent(name)
  return markRaw(c)
}

const componentList = [
  'GlobalComponentA',
  'GlobalComponentB',
  'LocalComponentA',
  'LocalComponentB',
].map(lookupComponent)
</script>

<template>
  <component :is="c" v-for="c in componentList" />
</template>

demo 2

Note: markRaw is used on the component definition because no reactivity is needed on it.

When using script setup, you need to reference the component and not the name or key.

To get it to work, I would use an object where the string can be used as a key to target the component from an object like this:

<script setup>
    import CompA from './CompA.vue'
    import { ref } from 'vue'
    const components = {CompA};

    // I do NOT want to use [CompA, CompA] because my inputs are strings
    const componentTreeName = ["CompA", "CompA"]
</script>

<template>
  <h1>Demo</h1>
  <template v-for="compName in componentTreeName">
    <component :is="components[compName]"></component>
  </template>
</template>

To use a global component, you could assign components by pulling them from the app context. But this would require the app context to be available and the keys known.

example:

import { app } from '../MyApp.js'
const components = {
  CompA: app.component('CompA')
}

I haven't tested this, but this might be worth a try to check with getCurrentInstance

import { ref,getCurrentInstance } from 'vue'
const components = getCurrentInstance().appContext.components;
Related