I have following three Vue components using the Composite API with the setup option. Where I want to access data in a nested tree.
The following code is a simplification of the code I need to integrate it. Basically the app is following the Atom Design principals (see also https://atomicdesign.bradfrost.com/chapter-2/).
// Wrapper.vue
// High level component composing smaller ones -> Organism
<template>
<div>Wrapper Text</div>
<Comp1 :isEdit="isEdit" :dataArr="data">
<div class="some css">{{item.name}} – {{item.description}}</div>
</Comp1>
</template>
<script lang="ts" setup>
import {ref} from 'vue';
import {Comp1} from './components';
const isEdit = ref(true);
const data = [
{name: 'Item 1', value: 'item-1', description: 'Item 1 description'},
{name: 'Item 2', value: 'item-2', description: 'Item 2 description'},
…
]
</script>
// Comp1.vue
// Composition consisting of one or more small components -> Molecule
<template>
<div>Foo Bar</div>
<div v-if="!isEdit">{{text}}</div>
<Comp2 v-if="isEdit" :dataArr="data">
<slot></slot>
</Comp2>
</template>
<script lang="ts" setup>
import {default as Comp2} from './Comp2.vue'
withDefaults(defineProps<{isEdit: boolean, dataArr: anx[]}>)(), {isEdit: false, dataArr: () => []})
const text = "Hello World"
</script>
// Comp2.vue
// Simple component rendering data -> Atom
// You could argue here if it is an atom or not, but this is not the point here ;)
<template>
<div>Comp 2 Text</div>
<div v-for="item in dataArr">
<slot></slot>
</div>
</template>
<script lang="ts" setup>
withDefaults(defineProps<{dataArr: any[]}>)(), {dataArr: () => []})
</script>
I want to define a template in the wrapper component where I define how to render data which are available in Comp2 but haven't found a way yet how to do so. I had a look into provide / inject but haven't seen how this could help.
Is there a way how I can access the item in the template which is defined in the wrapper component? If the solution is with provide / inject, please point me in the right direction. :)
You can use scoped slot to do something like this here, a demo I made Link
Related
I have the following View in Vue:
<script setup>
import Overwrite from "../components/Overwrite.vue";
</script>
<template>
<div>
...
<textarea v-model="text" cols="99" rows="20"></textarea>
...
</div>
</template>
<script>
export default {
data() {
return {
text: ""
};
},
components: { Overwrite: Overwrite },
};
</script>
Everything works perfectly fine when I start the application with npm run dev.
However, when I build the app for production and run it, I get the following error as soon as I type anything into the textarea:
index.57b77955.js:3 Uncaught ReferenceError: text is not defined
at HTMLTextAreaElement.t.onUpdate:modelValue.s.<computed>.s.<computed> [as _assign] (index.57b77955.js:3:1772)
at HTMLTextAreaElement.<anonymous> (vendor.31761432.js:1:53163)
I also have other form elements that show the exact same behaviour.
You can use a maximum of 1 × <script> tag and a maximum of 1 × <script setup> per vue component.
Their outputs will be merged and the object resulting from merging their implicit or explicit exports is available in <template>.
But they are not connected. Which means: do not expect any of the two script tags to have visibility over the other one's imports.
The worst part is that, although the first <script setup> does declare Ovewrite when you import it (so it should become usable in <template>), the second one overwrites it when you use components: { Overwrite: Overwrite }, because Overwrite is not defined in the second script. So your components declaration is equivalent to:
components: { Overwrite: undefined }
, which overwrites the value already declared by <script setup>.
This gives you two possible solutions:
Solution A:
<script>
import Overwrite from "../components/Overwrite.vue";
export default {
components: {
Overwrite
},
// you don't need `data` (which is Options API). use `setup` instead
setup: () => ({
text: ref('')
})
}
</script>
Solution B:
<script setup>
import Overwrite from "../components/Overwrite.vue";
const text = ref('')
</script>
Or even:
<script setup>
import Overwrite from "../components/Overwrite.vue";
</script>
<script>
export default {
data: () => ({ text: "" })
};
</script>
Can you try using only the setup script tag? Using it only for imports this way doesn't make sense. If you import a component in setup script tags you don't need to set components maybe the issue is related to that.
Also you could try the full setup way then:
<script setup>
import { ref } from 'vue'
import Overwrite from "../components/Overwrite.vue";
const text = ref('')
</script>
<template>
<div>
...
<textarea v-model="text" cols="99" rows="20"></textarea>
...
</div>
</template>
I have created the basic Vue component which is as follow.
<template>
<div class="example">
/* here render the component or HTML passed by pluing */
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
name: 'Example',
});
</script>
Now I need to create a plugin that will render any HTML or Component passed to the Example.I know the basic syntax of plugins and how to use them in the Vue but don't know how to do this task since I am a newbie to Vue.
export default {
install(vue, opts){
/* how to take the component or HTML and pass to `Example` Component */
}
}
I need to implement these in vue2.
You can use a slot to render an arbitrary HTML inside any of your components.
Your Example Component:
<template>
<div class="example">
<slot />
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
name: 'Example',
});
</script>
Then use it like this:
<template>
<example>
<div>Render me inside the slot</div>
</example>
</template>
And it will result in the following rendered HTML:
<div class="example">
<div>Render me inside the slot</div>
</div>
Then in you plugin you can use your Example component and pass any html you want to it
Update: this how your plugin might look like. Just create a global component inside of it
import Vue from 'vue'
const ExamplePlugin = {
install(Vue){
Vue.component('Example', {
name: 'Example',
template: '<div class="example"><slot /></div>'
})
}
}
Then you can Vue.use(ExamplePlugin) and Example component will be available globally in your application
Problem: I am trying to programmatically register a component to be used in a slot in my Vue/Nuxt site. The component name is included in the data of the parent index.vue file, in this instance the component is named Projects. I am including it in a v-for template as the various objects in the 'modules' data array are iterated over. I had assumed this would be possible/easy from the dynamic component documentation and example however I have not managed to get it working in my case.
What I expect to happen: I expected the component to be registered and 'slotted' into the Module component correctly.
What actually happens: While I can see on the rendered view that the component is 'there', it is not in the correct place (i.e. slotted into the Module component). I also get a vue/no-unused-components error saying: The "Projects" component has been registered but not used.
I have read the documentation about component registration in modular systems but these seem to be for more complex cases than what I am trying to achieve. Any advice would be really helpful as I am totally stuck!
index.vue
<template>
<div>
<template v-for="module in modules">
<Module
:title="module.title"
:to="module.link"
/>
<component v-bind:is="module.slot" />
</Module>
</template>
</div>
</template>
<script>
import Module from '~/components/module/Module.vue'
import Projects from '~/components/module/slots/Projects.vue'
export default {
components: {
Module,
Projects
},
data () {
return {
modules: [
{
title: 'Work',
slot: 'Projects'
},
{
...
}
]
}
}
}
</script>
Edit: As a side note, I get the same error when registering the component with import like so:
components: {
Module,
'Projects': () => import('#/components/module/slots/Projects')
}
Module.vue
<template>
<div>
<h2>
{{ title }}
</h2>
<slot />
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
default: ''
}
}
}
</script>
Projects.vue
<template>
<div>
<h3>Projects</h3>
</div>
</template>
<script>
export default {
name: 'Projects'
}
</script>
You use the self closure tag in your Module component.
This prevents your Projects component to be rendered within the slot.
Just replace:
<Module
:title="module.title"
:to="module.link"
/>
with:
<Module
:title="module.title"
:to="module.link"
>
and it should work.
I have two components, an item component and a folder component. Each item contains it's own folder component, and each folder component contains a list of items.
So I am trying to use the item component in the folder component, and vice versa, however, I am getting the error: unknown custom element: <item> - did you register the component correctly? For recursive components, make sure to provide the "name" option. Despite the fact that I have the name option set on both components.
Any ideas?
Code is below
item.vue
<template>
<div class="item" style="height: 30px">
. . .
<folder v-if="item.hasChildren" :id="id"></folder>
</div>
</template>
<script scoped>
import Folder from './folder.vue';
export default {
name: 'item',
props: ['id'],
components: {
Folder
}
};
</script>
folder.vue
<template>
<div class="folder">
<template v-for="(child, index) in children">
<item :last-item="index === children.length - 1" :key="child.id" :id="child.id"></item>
</template>
</div>
</template>
<script>
import Item from "./item.vue";
export default {
name: 'folder',
props: ['id', 'hasChildren'],
components: {
Item
}
};
</script>
This is probably happening because of the circular referencing between your components ..
When you look closely, you’ll see that these components will actually
be each other’s descendent and ancestor in the render tree - a
paradox!
To resolve this paradox you can either register your components globally using Vue.component, or you can defer the import of one of your components to a later point (by moving your import to the beforeCreate hook or using async components as demonstrated here) ..
folder.vue
<template>
<div class="folder">
<template v-for="(child, index) in children">
<item :last-item="index === children.length - 1" :key="child.id" :id="child.id"></item>
</template>
</div>
</template>
<script>
export default {
name: 'folder',
props: ['id', 'hasChildren'],
components: {
'item': () => import('./item.vue')
}
};
</script>
You need to provide the key (name) in the components object like so,
item.vue
components: {
folder: Folder
}
folder.vue
components: {
item: Item
}
Say, I have the following single file component in Vue:
// Article.vue
<template>
<div>
<h1>{{title}}</h1>
<p>{{body}}</p>
</div>
</template>
After importing this component in another file, is it possible to get its template as a string?
import Article from './Article.vue'
const templateString = // Get the template-string of `Article` here.
Now templateString should contain:
<div>
<h1>{{title}}</h1>
<p>{{body}}</p>
</div>
It is not possible.
Under the hood, Vue compiles the templates into Virtual DOM render functions.
So your compiled component will have a render function, but no place to look at the string that was used to generate it.
Vue is not a string-based templating engine
However, if you used a string to specify your template, this.$options.template would contain the string.
set ref attribute for your component, and then you can get rendered HTML content of component by using this.$refs.ComponentRef.$el.outerHTML, and remember don't do this when created.
<template>
<div class="app">
<Article ref="article" />
</div>
</template>
<script>
import Article from './Article.vue'
export default {
name: 'App',
data() {
return {
templateString: ""
}
},
components: {
Article,
},
created() {
// wrong, $el is not exists then
// console.log(this.$refs.article.$el.outerHTML)
},
mounted() {
this.templateString = this.$refs.article.$el.outerHTML
},
}
</script>