VueJS does not update DOM after fetch data from API? - vue.js

I am trying to create an example about the list of photos and I see a trouble when binding data to the component after call API.
JS code:
<script>
// photo item
Vue.component('photo-item', {
props: ['photo'],
template: `<li>{{ photo.name }}</li>`
});
// List of photos
Vue.component('photo-list', {
props: ['photos'],
template: `
<ul id="photo-list">
<photo-item v-for="photo in photos" :photo="photo"></photo-item>
</ul>`
});
new Vue({
el: "#photo_detail",
data: {
photos: []
},
created: function() {
axios
.get('/api/photos')
.then(function (response) {
this.photos = response.data; // Data existed
})
.catch(function (err) {
console.log(err);
});
}
})
</script>
HTML code
<main id="photo_detail">
<photo-list v-for="photo in photos" :photo="photo"></photo-list>
</main>
After fetching all photos from API and as my understand then the variable photos will auto binding and VueJS will update DOM.
VueJs 2.1.6
Any help.
Thanks!

Issue is with your this value inside function() which has this value scoped to axios instead of vue instance .
or you can use (response)=> to use this directly
new Vue({
el: "#photo_detail",
data: {
photos: []
},
created: function() {
var self=this;
axios
.get('/api/photos')
.then(function (response) {
self.photos = response.data; // Data existed
})
.catch(function (err) {
console.log(err);
});
}
})

Your code is not correct.
Problems:
It will be better to define used components for each component, like
components: {photoItem}.
In your axios callback you use function and that means, that you use wrong context inside (this.photos). Use arrow function
(() => {}) instead of the function () {}
The directive v-for requires directive :key=""
I've fixed it below.
// photo item
const photoItem = Vue.component('photo-item', {
props: ['photo'],
template: `<li>{{ photo.name }}</li>`
});
// List of photos
const photoList = Vue.component('photo-list', {
// define used components
components: {photoItem},
props: ['photos'],
template: `
<ul id="photo-list">
<!-- add :key="" directive -->
<photo-item v-for="(photo, index) in photos" :key="index" :photo="photo"></photo-item>
</ul>`
});
new Vue({
el: "#photo_detail",
// define used components
components: {photoList},
data: {
photos: []
},
created: function() {
// axios.get('/api/photos')
// use arrow function
setTimeout(() => {
this.photos = [{name: 'Photo 1'}, {name: 'Photo 2'}];
}, 1000);
}
})
<script src="https://unpkg.com/vue"></script>
<main id="photo_detail">
<photo-list :photos="photos"></photo-list>
</main>

Related

get returned data to display inside of vue model

I have a simple set of data that will come in from my method. I need the data to display inside of my vue model thats all which is inside an object or array. Honestly, any that works. Normally, just adding vm.array name or object name works on success but cannot get it to display.
new Vue({
el: "#app",
data: {
mydata:{}
},
methods: {
getTokenData(){
$.ajax({
url: "https://jsonplaceholder.typicode.com/posts/1",
success: function (data, status) {
alert("success");
console.log(data);
this.mydata=data;
},
mounted: function(){
this.getTokenData();
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
{{mydata}}
</div>
This might work well
new Vue({
el: "#app",
data() {
return {
mydata: {}
}
},
methods: {
getTokenData(){
axios
.get("https://jsonplaceholder.typicode.com/posts/1")
.then((response) => {
// this.mydata=data;
if (response.status) {
this.mydata = response.data;
console.log("myData ===>", this.mydata);
}
});
}
},
mounted: function(){
this.getTokenData();
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<div id="app">
title: {{mydata.title}}
<br>
body: {{mydata.body}}
</div>

Props arguments are not reactive in setup

I'm trying to develop a component that compiles the given html; I succeeded with constant html texts, but I can't make this work with changing html texts.
main.js
app.component("dyno-html", {
props: ["htmlTxt"],
setup(props) {
watchEffect(() => {
console.log(`htmlText is: ` + props.htmlTxt);
return compile(props.htmlTxt);
});
return compile(props.htmlTxt);
},
});
Home.js
<template>
<div class="home">
<dyno-html
:htmlTxt="html2"
:bound="myBoundVar"
#buttonclick="onClick"
></dyno-html>
-------------
<dyno-html
:htmlTxt="html"
:bound="myBoundVar"
#buttonclick="onClick"
></dyno-html>
</div>
</template>
<script>
export default {
name: "Home",
components: {},
data: function() {
return {
html: "",
html2: `<div> Static! <button #click="$emit('buttonclick', $event)">CLICK ME</button></div>`
};
},
mounted() {
// get the html from somewhere...
setTimeout(() => {
this.html = `
<div>
Dynamic!
<button #click="$emit('buttonclick', $event)">CLICK ME</button>
</div>
`;
}, 1000);
},
methods: {
onClick(ev) {
console.log(ev);
console.log("You clicked me!");
this.html2 = "<b>Bye Bye!</b>";
},
},
};
</script>
Outcome:
Console:
It seems the changes of htmlText arrives to setup function, but it doesn't affect the compile function!
This is the expected behaviour because prop value is read once and results in static render function.
Prop value should be read inside render function. It can be wrapped with a computed to avoid unneeded compiler calls:
const textCompRef = computed(() => ({ render: compile(props.htmlTxt) }));
return () => h(textCompRef.value);

Vue2 create component based on data

I want to create a component based on ajax api response or data which include:
template
data
methods - there may be several methods
Remark: response or data is dynamic and it is not saved in file.
I have tried to generate and return result like :
<script>
Vue.component('test-component14', {
template: '<div><input type="button" v-on:click="changeName" value="Click me 14" /><h1>{{msg}}</h1></div>',
data: function () {
return {
msg: "Test Componet 14 "
}
},
methods: {
changeName: function () {
this.msg = "mouse clicked 14";
},
}
});
</script>
and do compile above code :
axios.get("/api/GetResult")
.then(response => {
comp1 = response.data;
const compiled = Vue.compile(comp1);
Vue.component('result-component', compiled);
})
.catch(error => console.log(error))
I got error on Vue.compile(comp1) -
Templates should only be responsible for mapping the state to the UI. Avoid placing tags with side-effects in your templates, such as
<script>, as they will not be parsed.
Thanks in advance
Your Api should return a JSON with every property required by a Vue component (name, data, template, methods), note that methods needs to be converted into an actual js function (check docs about that)
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: '#app',
data() {
return {
apiComponent: { template: '<div>Loading!</div>' }
};
},
methods: {
loadApiComponent() {
setTimeout(() => {
this.buildApiComponent(JSON.parse('{"name":"test-component14","template":"<div><input type=\\\"button\\\" v-on:click=\\\"changeName\\\" value=\\\"Click me 14\\\" /><h1>{{msg}}</h1></div>","data":{"msg":"Test Componet 14 "},"methods":[{"name":"changeName","body":"{this.msg = \\\"mouse clicked 14\\\";}"}]}'));
}, 2000);
},
buildApiComponent(compObject) {
const {
name,
template,
data,
methods
} = compObject;
const compiledTemplate = Vue.compile(template);
this.apiComponent = {
...compiledTemplate,
name,
data() {
return { ...data
}
},
methods: methods.reduce((c, n) => {
c[n.name] = new Function(n.body);
return c;
}, {})
};
}
},
mounted() {
this.loadApiComponent();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<component :is="apiComponent" />
</div>

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

VueJs Nested props coming through undefined

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>