Vue.js dynamic component with dynamic data - javascript

I'm trying to use the <component> element to dynamically display a chosen component. Each of these displayed components can take one of any number of data objects. Something like:
<div id="containers">
<component v-bind:is="currentView"></component>
</div>
var myVue = new Vue({
el:"#containers",
data:{
currentView: "my-component-one",
currentData: {...}
},
method: {
changeView: function(){
//change this.currentView
//change this.currentData
}
}
});
However, the Vue documentation says the v-bind:is attribute can be used to pass either a component name or the options object.
It is unclear how I would conditionally get an object of values for that component to use and also conditionally change which component is shown.
I am very green with using Vue (coming fresh off a knockout kick) so perhaps I am simply misunderstanding the intention of the component tag.

you can simply use v-bind
html
<component v-bind:is="currentView" v-bind="data"></component>
script
data()
{
return {
data: {
currentData: "example"
}
}
}
and it will pass currentData down to child. You can also add other properties along with it, including is.
If you need to change the component along with props, then you just change the data property, or whatever you want to call it.
https://codesandbox.io/s/7w81o10mlx

This example might help you understand it. https://jsfiddle.net/jacobgoh101/mLbrj5gd/
For passing Component Name
If the component is global, you can pass the component name to v-bind:is
for e.g.,
Vue.component('test1', {
template: `<div>test1</div>`
})
HTML
<component is="test1"></component>
For passing option
A Vue component is literally just a Javascript object with specific properties
for e.g.,
<component v-bind:is="{
template: `<div>test2</div>`
}"></component>

Related

Instanciating extended VueJS components with props data within

Hello everyone,
Let me give you a little bit of context about my problem :
I'm trying to create a system that can add charts on a page with the simple push of a button.
These charts are going to contain elements from a MySQL database.
I have a Chart.vue file that contains the template for a single HighChart element. It also contains a prop :
export default {
name : "Chart",
props : ["tableToDisplay"],
And then I have my main vue that is named "Test.vue".
It imports the Chart.vue from the component folder and then I basically just need to write :
<Chart :table-to-display="tableToDisplay"/>
to create an instance of a chart of the table contained within the variable : this.tableToDisplay.
But this is not what I want to do : I want to create a chart with the push of a button, so I made some changes :
<div>
<button #click="createGraph">Add a graph</button>
<Chart :table-to-display="tableToDisplay"/>
</div>
And with it, I created the method:
createGraph(event)
{
let ChartClass = Vue.extend(Chart)
console.log(ChartClass)
let graphInstance = new ChartClass({
props:{
"tableToDisplay": this.tableToDisplay
}
})
graphInstance.$mount()
let divContainer = event.target.parentElement
divContainer.append(graphInstance.$el)
},
That is where my problem is.
Within that method, I want to send a table to display to the newly created Chart, but it seems that I can't manipulate the props value in that way.
I thought that this piece of code was the solution :
let graphInstance = new ChartClass({
props:{
"tableToDisplay": this.tableToDisplay
}
})
But It turns out that it is not.
When I click the button, an empty chart does appear but the prop "tableToDisplay" is undefined.
I looked at the console and I get a "[Vue warn]: Error in the mounted hook: "TypeError: ciphertext is null".
It doesn't matter if I put an argument or not in the ChartClass, I always have this error on the graphInstance.$mount() line.
First, I think you don't need to programatically instantiate your Chart components. A simple v-for loop will do the trick:
<template>
<Chart v-for="table of chartTables :table-to-display="table"/>
</template>
<script>
...
data() {
chartTables: []
},
methods: {
createChart() {
// Adding a new table to the array will create a new Chart component.
this.chartTables.push(this.tableToDisplay)
}
}
</script>
If this solution suits your needs, go ahead in that way!
That said, if you really need to instantiate a Vue component yourself, you have to use the propsData parameter to pass your props.
const instance = new ChartClass({
parent: this, // The parent is your current component
propsData: {
tableToDisplay: this.tableToDisplay,
},
})
let divContainer = event.target.parentElement
instance.$mount(divContainer)
The parent option is really important: it adds your component to the Vue component dependency tree. Without it, your component won't have inherited properties (such as the Vuex store, plugins etc.).

Vue - Access child component default props after mounted

I'm trying to figure out what the default properties (props) were for a child component. In this example, I have two components A and B. B wraps around A and A is passed properties. I'd like to figure out what the default values were for the component A from the component B which wraps around it, most importantly the types specified for the defaults. To be clear, in the mounted function of B I'd like to be able to see the default values and types of A assuming B will always have exactly 1 child component.
I've tried getting the child component using this.$children[0]._props in the mounted lifecycle hook, but those properties are the ones set. I've tried getting the properties earlier in the Vue lifecycle (like created, beforeCreate, beforeMount etc.) except they don't seem to exist until mounting. I've also inspected the this.$children[0] object using the browser console, and haven't found any default values for the props (only getter functions which retrieve the override defaults). I'm willing to pass extra data to B in order to get the default properties, but would prefer not to (since it seems redundant, i.e. I should know what the component "origin" was by looking at the this.$children[0] object).
Minimal example is located here: https://codepen.io/maxschommer/pen/wvaqGjx
I've included the HTML and JS below for quick reference.
JS:
Vue.component('A', {
name: "A",
props: {
prop1: {
type: String,
default: "The First Default Value"
},
prop2: {
type: String,
default: 'The Second Default Value'
}
},
template: `<div class="A">
<h1>A Prop 1: {{ prop1 }} </h1>
<h1>A Prop 2: {{ prop2 }} </h1>
</div>`
});
Vue.component('B', {
name: "B",
mounted: function() {
this.$children[0];
// I'd like to find some way to get the default properties
// of the child of this component (A) here (or in computed, etc.). Instead
// this gives the values which were set.
alert(JSON.stringify(this.$children[0]._props));
},
template:
`<div><slot></slot></div>`});
var parent = new Vue({
el: "#app",
template:
`<div class=templateField>
<B>
<A prop1="Overriding" prop2="Defaults"></A>
</B>
</div>`
});
HTML:
<div id="app">
</div>
PS: I'm a bit confused about the difference between components and elements when refering to Vue (I believe components are JS objects and elements are when they are rendered to html) so please correct my terminology if I'm getting it wrong.
You can access the original options object (the object you give Vue to construct component instances) from this.$options, so
mounted() {
const propDfns = this.$options.__proto__.props
const propTypes = Object.values(propDfns).map(p => p.type.name)
console.log(propTypes)
},
Components are not only JS objects. they are mixture of js, html or template and css

Why is this Vue prop not reacting to change?

I have a Prop in my component that is a User object, I then have this function:
onChange: function(event){
this.$v.$touch();
if (!this.$v.$invalid) {
this.$axios.put('update',{
code:this.user.code,
col:event.target.name,
val:event.target.value
}).then(response => {
this.user[event.target.name]=event.target.value
});
}
}
I can see in the Vue console debugger that the correct attribute has been updated, this attribute exists when the component is created but the template where I reference it does not refresh:
<p>Hi {{user.nickname}}, you can edit your details here:</p>
This is all within the same component so I'm not navigating to child or parent. I'm sure props have been reactive elsewhere in my code?
Ok, it seems this is intended behaviour. According to the documentation
https://v2.vuejs.org/v2/guide/components-props.html in the scenario that I have it should be handled as:
The prop is used to pass in an initial value; the child component wants to use it as a local data property afterwards. In this case,
it’s best to define a local data property that uses the prop as its
initial value:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
Usually components should be reactive to Props, though i have had experiences where it was non-reactive so i added the prop to a watcher and put the functional call there.
props: ["myProp"],
watch:{
myProp(){
// ... your functions here
}
}

Can a v-on directive directly modify a data() property of the parent?

I have an application which has Vue components as children.
The components pass back data to the parent via a this.$emit (numberchnaged below), which is caught at the parent level by a v-on (or #) directive, which in turns triggers a method.
This method then updates a data() property of the parent:
<template>
(...)
<Users #numberchanged="doNumCh"></Users>
(...)
</template>
<script>
(...)
export default {
components: {
Users
},
data() {
return {
u: "hello"
}
},
methods: {
doNumCh(value) {
this.u = value
}
}
}
</script>
This solution works but is quite verbose for just updating this.u with what <Users> sent back.
Is there a way to make the update right in the <Users> tag, something like
<Users #numberchanged="u=theValueReturedByUsers"></Users>
My problem is that I do not know how to extract theValueReturedByUsers, I only get hold of it in the method as value (in my example above).
Functionally, you're looking to have v-model behavior on your component. Vue provides for that. So you can say
<template>
(...)
<Users v-model="u"></Users>
(...)
</template>
which is a tidy view, as long as your Users component (side note: you should always have a hyphen in custom component names) takes the value parameter and $emits the input event.
See also v-bind.sync to work with props other than value.
The payload is reachable via $event.
For the code above, the solution would therefore be
<Users #numberchanged="u=$event"></Users>
You can do it like this (without write a method in parent) with using the variable $event which contains the value returned (Object or literal variable) from child component:
<users #numberchanged="{u=$event}"></users>

Passing a global var to a vue component attribute

I'm trying to pass php/laravel data to my component from a custom global variable. I've seen examples of this going into the new Vue({}) area directly but I haven't seen any way to pass this by going into right into the component
<script>
var itemData = //json object
</script>
<custom-component item-data="ITEMDATAVAR"></custom-component>
I should specify that I do have item-data in my component props. The issue is that I'm not sure how to tell my component's html that I'm passing the value of the variable itemData and not the string "itemData"
I think you are referring to dynamic props
<custom-component v-bind:item-data="ITEMDATAVAR"></custom-component>
or use the shorthand syntax
<custom-component :item-data="ITEMDATAVAR"></custom-component>
You should add the item-data to the props array like this:
Vue.component('custom-component', {
props: ['item-data'],
...
}
You can research this Vue.js example
Create a variable
new Vue({
el: '#el',
data: yourJsonObject
})
In you component you have to write about props
Vue.component('custom-component', {
props: ['item-data']
...
}
Pass the data to the component the same way
<custom-component item-data="ITEMDATAVAR"></custom-component>
I have not tested how it will work, guided by the documentation.

Categories