Adding a component by clicking a button - vue.js

Vue.component('component-a', {
template: '<h3>Hello world!</h3>'
})
new Vue({
el: "#app",
data: {
arr: []
},
methods: {
add(){
this.arr.push('component-a');
console.dir(this.arr)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<component-a></component-a>
<hr>
<button #click="add">Add a component</button>
<ul>
<li v-for="component in arr"> {{ component }} </li>
</ul>
</div>
I want to insert a component a lot of times to the page by clicking a butoon, but instead of this only a component`s name is inserted. How to add a component itself?

In your code the double curly braces do not reference the component itself but just the string you added with this.arr.push('component-a'); hence just the string being displayed.
If you would like this string to call the actual component you could use dynamic components.
Replacing {{ component }} with <component :is="component"/> would achieve the effect I think you're looking for.
However if you're only going to be adding one type of component I would consider adding the v-for to the component tag itself like so:
<component-a v-for="component in arr/>

Use the component element to render your component dynamically.
The usage is very simple: <component :is="yourComponentName"></component>
The ":is" property is required, it takes a string (or a component definition).
Vue will then take that provided string and tries to render that component. Of course the provided component needs to be registered first.
All you have to do is to add the component tag as a child element of your list tag:
<li v-for="component in arr">
<component :is="component"></component>
</li>
Vue.component('component-a', {
template: '<h3>Hello world!</h3>'
})
new Vue({
el: "#app",
data: {
arr: []
},
methods: {
add() {
this.arr.push('component-a');
console.dir(this.arr)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<component-a></component-a>
<hr>
<button #click="add">Add a component</button>
<ul>
<li v-for="component in arr">
<component :is="component"></component>
</li>
</ul>
</div>

Related

Nuxt / Vue : Component call a Method in a page

There is my issue:
My page listing.vue list all products.
Theses products are in a Component, Product.vue.
In this component, there is a button to add this product to a selection, displaying on the listing.vue.
page/listing.vue :
<template>
<div>
<product v-for="...." />
<section>
<ul>
<li>Produit 1</li>
<li>Produit 3</li>
</section>
</div>
</template>
<script>
export default {
methods: {
addToSelection(id) {
// Code to add Product to <ul> //
}
}
}
</script>
component/product.vue:
<template>
<div>
{{ product.title }}
<button #click="addToSelection(product.id)">
Add product to selection
</button>
</div>
</template>
<script>
export default {
props: ['product'],
}
</script>
The problem is nuxt render an error:
the addToSelection method is unknown.
You should emit from your children to your parent
Product.vue
<button #click="emitProductToParent(product.id)">
...
methods: {
emitProductToParent(id) {
this.$emit('input', id)
}
}
Listing.vue
<Product #input="addToSelection" v-for="...." />
You cannot use a method that is not in the component your event listener is on. And even if you could, this is not the way to do. Use:
props to pass things down to children
emit to pass things up to parents
As explained in the official documentation here: https://v2.vuejs.org/v2/guide/components.html#Listening-to-Child-Components-Events

Component inside `transition-group` is rendered twice (duplicated)

Here is a strange issue i am facing.
<script src="https://unpkg.com/vue#2.6.11/dist/vue.min.js"></script>
<div id="app">
<transition-group name="fadeLeft" tag="ul">
<section key="0">
<testt></testt>
<template v-pre id="myId">
<div>My neighbors: <a v-for="(val,index) in myArray">{{val}}</a></div>
</template>
</section>
</transition-group>
</div>
<script>
Vue.component('testt', {
template: '#myId',
props: {
myArray: {
type: Array,
default: function() {
return ['James', 'Mike'];
}
}
}
})
new Vue({
el: '#app'
})
</script>
transition-group renders the component twice (and the second rendering is done without parsed "{{variable}}").
If you just remove transition-group parent at all, it works as expected and there is no duplicated content. So, definitely the problem seems somewhere there. How to fix that (so, retain transition-group and solve the problem)
Please don't post answers "use component outside of app" or similar offtopic, i described the problem I need to find answer. Also, the aprior is that the template needs to be within transition-group decendants.
The problem:
In the section tack you have the testt tag which was rendered with the parsed HTML and also the template which was rendered as another literal tag (no rendering). And since transition-group elements must be keyed, the template had to be moved out.
The solution:
<script src="https://unpkg.com/vue"></script>
<div id="app">
<transition-group name="fadeLeft" tag="ul">
<section key="0">
<testt></testt>
</section>
</transition-group>
<template v-pre id="myId">
<div>My neighbors: <a v-for="(val,index) in myArray">{{val}} </a></div>
</template>
</div>
<script>
Vue.component('testt', {
template: '#myId',
props: {
myArray : { type:Array, default :function(){ return ['James','Mike'];} }
}
})
new Vue({
el: '#app'
})
</script>

Nesting a slot in a slot for vue

Update: Here's a simplified version of what I'm trying to achieve here (from the threaded conversation below):
Accept Component A - Accept Component B - Accept a condition - if
condition is true : wrap Component B with Component A [and render]- else only
render component B.
I'm interested in creating a component that renders a wrapper conditionally. I figured a theoretical approach like this would probably be best**:**
<template>
<div>
<slot v-if="wrapIf" name="wrapper">
<slot name="content"></slot>
</slot>
<slot v-else name="content"></slot>
</div>
</template>
<script>
export default {
props: {
wrapIf: Boolean,
}
}
</script>
Then when we implement, it would look something like this:
...
<wrapper-if :wrap-if="!!link">
<a :href="link" slot="wrapper"><slot></slot></a>
<template slot="content">
content
</template>
</wrapper-if>
The idea being that, in this case, if there is a link, then let's wrap the content with the wrapper slot (which can be any component/element). If there isn't, then let's just render the content without the wrapped link. Pretty simple logic, but it seems that I'm misunderstanding some basic vue functionality because this particular example does not work.
What is wrong with my code or is there some kind of native api that already achieves this or perhaps a dependency that does this sort of thing already?
The output should look like this:
wrapIf === true
<a href="some.link">
content
</a>
wrapIf === false
content
Just focus on the content itself, and let the component worry about whether or not to wrap the default or named content slot.
If you need the wrapper to be dynamic, a dynamic component should solve that. I've updated my solution accordingly. So if you need the wrapper to be a label element, just set the tag property to it, and so on and so forth.
const WrapperIf = Vue.extend({
template: `
<div>
<component :is="tag" v-if="wrapIf" class="wrapper">
<slot name="content"></slot>
</component>
<slot v-else name="content"></slot>
</div>
`,
props: ['wrapIf', 'tag']
});
new Vue({
el: '#app',
data() {
return {
link: 'https://stackoverflow.com/company',
tagList: ['p', 'label'],
tag: 'p',
wrap: true
}
},
components: {
WrapperIf
}
})
.wrapper {
display: block;
padding: 10px;
}
p.wrapper {
background-color: lightgray;
}
label.wrapper {
background-color: lavender;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<wrapper-if :wrap-if="wrap" :tag="tag">
<a :href="link" slot="content">
content
</a>
</wrapper-if>
<div>
Change wrapper type:
<select v-model="tag">
<option v-for="tag in tagList">{{tag}}</option>
</select>
</div>
<button #click="wrap = !wrap">Toggle wrapper</button>
</div>

x-template has trouble displaying value on the v-for

I had this issue while trying to render html into a vue component.
I am trying to insert component html through x-template. The issue is when I was trying to display the value {{i.value}} like this it was throwing error on console.
<script type="text/x-template" id="first-template">
<div>
<ul>
<li v-for="i in dataCollection">{{ i.id }}</li>
</ul>
</div>
</script>
Vue.component('menu', {
template: '#first-template',
data() {
return {
dataCollection: [{"id":"01"}, {"id":"02"}, {"id":"03"}],
}
}
});
The error on console was:
But when I was giving value as attribute like:
<script type="text/x-template" id="first-template">
<div>
<ul>
<li v-for="i in dataCollection" :id="i.id"></li>
</ul>
</div>
</script>
it works perfect.
Anyone know any fix ?
You should not put script/x-template tages inside of the element that you mount to the main instance to. Vue 2.0 will read all of its content and try to use it as a template for the main instance, and Vue's virtualDOM treats script/x-template's like normal DOM, which screws everthing up,
Simply moving the template out of the main element solved the problem.
Source
This is a suggestion, not a answer.
As #DmitriyPanov mentioned, you'd better bind unique key when using v-for.
Another issue is you'd better to use non built-in/resevered html elements.
so change component id from menu to v-menu or else you like.
Then simulate similar codes below which are working fine.
I doubt the error is caused by some elements of dataCollection doesn't have key=id (probably you didn't post out all elements). You can try {{ 'id' in i ? i.id : 'None' }}.
Vue.component('v-menu', { //
template: '#first-template',
data() {
return {
newDataCollection: [{"id":"01"}, {"id":"02"}, {"id":"03"}, {'xx':0}],
dataCollection: [{"id":"01"}, {"id":"02"}, {"id":"03"}]
}
}
});
new Vue({
el: '#app',
data() {
return {testProperty: {
'test': '1'
}}
},
methods:{
test: function() {
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<v-menu></v-menu>
</div>
<script type="text/x-template" id="first-template">
<div>
<div style="float:left;margin-right:100px;">
<p>Old:</p>
<ul>
<li v-for="(i, index) in dataCollection" :key="index">{{ i.id }}</li>
</ul>
</div>
<div>
<p>Adjusted:</p>
<ul>
<li v-for="(i, index) in newDataCollection" :key="index">{{ 'id' in i ? i.id : 'None' }}</li>
</ul>
</div>
</div>
</script>
I think the problem here lies in the placement of the X-Template code (I had the same issue). According to the documentation:
Your x-template needs to be defined outside the DOM element to which Vue is attached.
If you are using some kind of CMS, you might end up doing just that.
What helped me in that case was (based on your example):
Placing the X-template script outside the #app
passing the collection as a prop to the v-menu component:
<v-menu v-bind:data-collection="dataCollection"></v-menu>
list dataCollection as a prop inside the v-menu component:
Vue.component('v-menu', { //
template: '#first-template',
props: [ "dataCollection" ],
...
});
I hope that helps anyone.
In 2.2.0+, when using v-for with a component, a key is now required.
You can read about it here https://v2.vuejs.org/v2/guide/list.html#v-for-with-a-Component

Vue dynamic component event bindings

I have a dynamic component that is resolved and bound on runtime using the documented dynamic component syntax;
<div class="field">
<component v-if="component" :is="component" #valueUpdated="onUpdate($event)"></component>
</div>
Decided using a prop assigned to on mounting.
For whatever reason, when the child component, rendered dynamically, emits an event this.$emit('eventName', /*someData*/) the parent does not seem to be able to hear it. Is the approach used in standard components applicable to those rendered dynamically? Props seem to work, so maybe I am not doing something correctly?
yeah you can use props with dynamic components it's just you need to use lowercase hyphenated (kebab-case) names for the html attribute, i.e.
<component v-if="component" :is="component" #value-updated="onUpdate"></component>
Vue.component('foo', {
template: `
<div>
<button #click="$emit('value-updated', { bar: 'baz' })">pass data</button
</div>
`
});
new Vue({
el: '#app',
data: {
currentComponent: 'foo',
output: {},
},
methods: {
onUpdate (payload) {
this.output = payload
}
}
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<component v-if="currentComponent" :is="currentComponent" #value-updated="onUpdate"></component>
<pre>{{ output }}</pre>
</div>