VueJs Nested props coming through undefined - vue.js

I am trying to access an array which is part of a prop (event) passed into a component, but when in created() or mounted() the array part of the event prop (the rest is fine) comes through as undefined.
As can be seen below, when I inspect the props in the vue chrome plugin, the registration_fields are there.
I can add a watcher to the event prop and can access the registration_fields that way, but this seems very awkward to have to do this to access already passed in data.
This is from the Chrome vue inspector:
event:Object
address1_field:"Some Address 1"
address2_field:"Some Address 2"
approved:true
registration_fields:Array[1]
This is what part of my vue file looks like:
export default {
props: ['event'],
data() {
return {
regFields: []
}
},
created() {
this.regFields = this.event.registration_fields // Undefined here!
},
watch: {
event() {
this.regFields = this.event.registration_fields //Can access it here
});
}
}
}
I am using Vue 2.4.4
This is how the component is called:
<template>
<tickets v-if="event" :event="event"></tickets>
</template>
<script>
import tickets from './main_booking/tickets.vue'
export default {
created() {
var self = this;
this.$http.get('events/123').then(response => {
self.event = response.data
}).catch(e => {
alert('Error here!');
})
},
data: function () {
return {event: {}}
},
components: {
tickets: tickets
}
}
</script>
Thank you

It actually works fine without the watcher.
new Vue({
el: '#app',
data: {
event: undefined
},
components: {
subC: {
props: ['event'],
data() {
return {
regFields: []
}
},
created() {
this.regFields = this.event.registration_fields // Undefined here!
}
}
},
mounted() {
setTimeout(() => {
this.event = {
registration_fields: [1, 3]
};
}, 800);
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<sub-c v-if="event" :event="event" inline-template>
<div>
{{regFields}}
</div>
</sub-c>
</div>
If, as Belmin Bedak suggests in the comment below, event is populated asynchronously, it comes in as undefined because it's undefined. In that case, you need a watcher, or, somewhat more elegantly, use a computed:
new Vue({
el: '#app',
data: {
event: {}
},
components: {
subC: {
props: ['event'],
computed: {
regFields() {
return this.event.registration_fields;
}
}
}
},
// delay proper population
mounted() {
setTimeout(() => { this.event = {registration_fields: [1,2,3]}; }, 800);
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<sub-c :event="event" inline-template>
<div>
{{regFields}}
</div>
</sub-c>
</div>

Related

How to pass dynamic props to vue's render function?

I try to render components dynamically based on descriptions.
From
{component: 'customComponent', props: {value: "val1"}, ...}
I'd render
<custom-component :value="val1" #input="v=>val1=v" />`
I aim to do this for arbitrary events and dynamic props.
I have no idea though how to pass dynamic props to render.
Partial solution:
A solution that works but re-renders everytime val1 changes, based on (https://symfonycasts.com/screencast/vue/vue-instance) is
render: function(h){
const template = "...building up the html here..."
return Vue.compile(template).render.call(this, h);
}
My attempt using the VueJS docs
I could not find in the docs on render how I could pass dynamic variables.
In the minimal implementation you can see how far I've got, if you can help me finish it, it would be awesome!
Minimal implementation so far
I expect to see 'hello' instead of 'values.value1' and values.value1 should update once I change the text in the text box.
demo.html:
<!DOCTYPE html>
<html>
<body>
<div id="q-app">
The text input should say 'hello' instead of 'values.value1'
<custom-component :descriptor="mainComponent"></custom-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#^2.0.0/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/quasar#1.15.15/dist/quasar.umd.min.js"></script>
<script>
Vue.component('custom-component', {
props: ['descriptor'],
render: function (createElement) {
const attributes = {
on: this.descriptor.events,
props: this.descriptor.props
}
return createElement(
this.descriptor.component,
attributes)
}
})
const app = new Vue({
el: '#q-app',
data: function(){
return {
mainComponent: {
component: 'q-input',
props: {
value: 'values.value1'
},
events: {
input: value => this.values.value1 = value
}
},
values: {
value1: 'hello'
}
}
}
})
</script>
</body>
I guess, I have fixed your example.
Of course, you can watch the value in the main app. But it is better to sent an input event with the value.
Added the reference to the component
ref="mc"
and the input event binding
#input="logEventValue"
Vue.component('custom-component', {
props: ['descriptor'],
render: function (createElement) {
const attributes = {
on: this.descriptor.events,
props: this.descriptor.props
}
return createElement(
this.descriptor.component,
attributes)
}
})
const app = new Vue({
el: '#q-app',
data: function(){
return {
mainComponent: {
component: 'q-input',
props: {
value: 'hello'
},
events: {
input: value => {
this.values.value1 = value;
// with ref="mc"
this.$refs.mc.$emit('input', value);
// or over the list of children
this.$children[0].$emit('input', value);
}
}
},
values: {
value1: 'hello'
}
}
},
watch: {
'values.value1': (newVal) => console.log(`1. watcher: ${newVal}`),
// or deeply
values: {
handler(newVal) {
console.log(`2. watcher: ${newVal.value1}`)
},
deep: true,
}
},
methods: {
logEventValue(value) {
console.log(`logEventValue: ${value}`);
}
}
})
<div id="q-app">
<custom-component ref="mc" #input="logEventValue" :descriptor="mainComponent"></custom-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#^2.0.0/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/quasar#1.15.15/dist/quasar.umd.min.js"></script>

I need fresh eyes to fix VUE props not working

This is a silly task in VUE.JS... but I'm missing it.
I have a sub parent component having:
<teamPlayers :teamId="team.id_team"></teamPlayers>
The value teamId is sent to the child component and it works: I can see the value in child template <h2>{{teamId}}</h2> properly.
But in same child component I got undefined inside the methods using this.teamId.
Here the whole child code:
export default {
props: ['teamId'],
methods: {
getJokess: function () {
console.log(this.teamId);
},
},
created() {
this.getJokess();
}
}
The console should return the correct value but it returns undefined instead of the {{teamId}} is render perfectly.
All that I can think of is that teams may not be declared in your data() function. If it isn't it won't be reactive. Consider the example below:
const teamPlayers = {
props: ["teamId"],
methods: {
getJokess() {
console.log(this.teamId);
}
},
created() {
this.getJokess();
},
template: "<h2>{{teamId}}</h2>"
};
const app = new Vue({
el: "#app",
components: {
"team-players": teamPlayers
},
data() {
return {
teams: []
};
},
mounted() {
setTimeout(() => {
this.teams = [{
id_team: "fizz"
},
{
id_team: "buzz"
},
{
id_team: "foo"
},
{
id_team: "bar"
}
]
}, 1000);
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="team of teams">
<team-players :team-id="team"></team-players>
</div>
</div>

listen to events from dynamic vue components

How would you listen to an event emitted by a dynamically created component instance?
In the example, the top component is added in the DOM, while the second is dynamically created in javascript.
Vue.component("button-counter", {
data: function() {
return {
count: this.initial_count
}
},
props: ['initial_count'],
methods: {
add: function() {
this.count++
this.$emit('myevent', this.count)
}
},
template: '<button v-on:click="add">You clicked me {{ count }} times.</button>'
})
let app = new Vue({
el: "#app",
data() {
return {
initial_count: 10,
}
},
mounted: function() {
let initial_count = this.initial_count
let ButtonCounterComponentClass = Vue.extend({
data: function() {
return {}
},
render(h) {
return h("button-counter", {
props: {
initial_count: initial_count
}
})
}
})
let button_counter_instance = new ButtonCounterComponentClass()
button_counter_instance.$mount()
button_counter_instance.$on('myevent', function(count) {
console.log('listened!')
this.say(count)
})
this.$refs.container.appendChild(button_counter_instance.$el)
},
methods: {
say: function(message) {
alert(message)
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<button-counter initial_count=20 v-on:myevent="say"></button-counter>
<div ref='container'></div>
</div>
If I've understood what you want then you just need to listen for the event on the inner component and pass it on.
I've used arrow functions in a couple of places to avoid problems with this bindings. Otherwise I've tried to leave your code unchanged as much as possible. Changes marked with ****.
Vue.component("button-counter", {
data: function() {
return {
count: this.initial_count
}
},
props: ['initial_count'],
methods: {
add: function() {
this.count++
this.$emit('myevent', this.count)
}
},
template: '<button v-on:click="add">You clicked me {{ count }} times.</button>'
})
let app = new Vue({
el: "#app",
data() {
return {
initial_count: 10,
}
},
mounted: function() {
let initial_count = this.initial_count
let ButtonCounterComponentClass = Vue.extend({
data: function() {
return {}
},
render(h) {
return h("button-counter", {
props: {
initial_count: initial_count
},
// **** Added this ****
on: {
myevent: count => {
this.$emit('myevent', count);
}
}
// ****
})
}
})
let button_counter_instance = new ButtonCounterComponentClass()
button_counter_instance.$mount()
// **** Changed the next line ****
button_counter_instance.$on('myevent', count => {
console.log('listened!')
this.say(count)
})
this.$refs.container.appendChild(button_counter_instance.$el)
},
methods: {
say: function(message) {
alert(message)
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<button-counter initial_count=20 v-on:myevent="say"></button-counter>
<div ref='container'></div>
</div>
It's important to understand that button_counter_instance is not an instance of your button-counter component. You've wrapped it in another component, albeit a component that doesn't add any extra DOM nodes. So listening on the wrapper component is not the same as listening on button-counter.
Docs for what you can pass to h: https://v2.vuejs.org/v2/guide/render-function.html#The-Data-Object-In-Depth

Passing data-attribute from Vue instance html tag

Is it possible to declare and pass a data-attribute value from a html tag of the Vue instance, and then have it available in the data object?
index.html:
<div id="app" data-title="My app title"></div>
App.vue:
data () {
return {
appTitle: // whatever is declared in data-title
}
}
This code works for me:
index.html:
<div id="app" data-id="123"></div>
index.js:
(function (el) {
new Vue({
el,
render: h => h(Module),
data: () => Object.assign({}, el.dataset) ,
});
})(document.getElementById('app'));
Module.vue:
export default {
name: 'Module',
data() {
return {
id: this.$parent.id,
};
},
};
Yes it is:
data () {
return {
appTitle: document.getElementById('app').dataset.title
}
}
However, it is possible that the DOM is not available on component initialization. So you should probably put that code into the mounted hook of your component:
<script>
export default {
data () {
return {
appTitle: null
}
},
mounted () {
this.appTitle = document.getElementById('app').dataset.title
}
}
</script>
Here's a different approach that doesn't rely on the DOM API, but cannot be used to get data-attributes from the root (#app) element:
{
el: '#app',
template: `
<div ref="mydiv" data-attribute="data attribute">
Hello from template
<div>
Hello from {{attribute}}
</div>
</div>`,
data(){
return {
attribute: ''
}
},
mounted(){
this.$data.attribute = this.$refs.mydiv.dataset.attribute;
}
});
Here's a pen with a working example

How to pass property to component used in main.js from other components in vuejs

Basically I want to a loadingbar component globally (included in app template)
Here is my loadingbar component
<template>
<div class="loadingbar" v-if="isLoading">
Loading ...
</div>
</template>
<script>
export default {
name: 'loadingbar',
props: ['isLoading'],
data () {
return {
}
}
}
</script>
<style scoped>
</style>
and in main.js, I have included this component as
import LoadingBar from './components/LoadingBar.vue';
new Vue({
router,
data () {
return {
isLoading: true
};
},
methods: {
},
created: function () {
},
components: {
LoadingBar
},
template: `
<div id="app">
<LoadingBar :isLoading="isLoading"/>
<router-view></router-view>
</div>
`
}).$mount('#app');
My aim is to show loading component based upon the value of variable isLoading. The above code working fine. But I want to use set isLoading variable from other component (so that to decide whether to show loading component). Eg. In post components
<template>
<div class="post container">
</div>
</template>
<script>
export default {
name: 'post',
data () {
return {
posts: []
}
},
methods: {
fetchPosts: function() {
// to show loading bar
this.isLoading = true;
this.$http.get(APIURL+'listpost')
.then(function(response) {
// to hide loading bar
this.isLoading = false;
console.log("content loaded");
});
}
},
created: function() {
this.fetchPosts();
}
}
</script>
<style scoped>
</style>
Of coarse we can't access isLoading directly from main.js so i decided to use Mixin so i put following code in main.js
Vue.mixin({
data: function () {
return {
isLoading: false
};
}
});
This however allow me to access isLoading from any other component but I can't modify this variable. Can any help me to achieve this?
Note: I know i can achieve this by including loadingbar in individual component (I tried that and it was working fine, But i do not want to do that as loadingbar is needed in every component so i was including in main template/component)
You could use Vuex like so:
// main.js
import Vuex from 'vuex'
let store = new Vuex.Store({
state: {
isLoading: false,
},
mutations: {
SET_IS_LOADING(state, value) {
state.isLoading = value;
}
},
getters: {
isLoading(state) {
return state.isLoading;
}
}
})
import LoadingBar from './components/LoadingBar.vue';
new Vue({
router,
store, // notice you need to add the `store` var here
components: {
LoadingBar
},
template: `
<div id="app">
<LoadingBar :isLoading="$store.getters.isLoading"/>
<router-view></router-view>
</div>
`
}).$mount('#app');
// script of any child component
methods: {
fetchPosts: function() {
// to show loading bar
this.$store.commit('SET_IS_LOADING', true);
this.$http.get(APIURL+'listpost')
.then(function(response) {
// to hide loading bar
this.$store.commit('SET_IS_LOADING', false);
console.log("content loaded");
});
}
},