<script>
const Detail = Vue.component('Detail', {
props: ['id'],
template:`
<div>
Pages: {{page}}
<p><b>ID:</b> {{ id }}</p>
<h1>{{page.title}}</h1>
<div v-if="page.content" v-html="page.content"></div>
</div>`,
data: function() {
return {
page : {}
};
},
created () {
console.log("Initial load with: " + this.id);
this.fetchData(); // this does not
},
watch: {
'$route': 'fetchData' // this works
},
methods: {
fetchData () {
console.log("Getting content for: " + this.id);
db.collection('pages')
.where("url", "==", this.id)
.get()
.then(function(querySnapshot) {
querySnapshot.forEach(function (documentSnapshot) {
data = documentSnapshot.data(); // I think is is what falls apart
});
});
this.page = data;
console.log(JSON.stringify(this.page));
}
}
}); // end component
Console results
Update
I added the following code
db.collection('pages')
.where("url", "==", this.id)
.get()
.then(function(querySnapshot) {
querySnapshot.forEach(function (documentSnapshot) {
console.log(JSON.stringify(documentSnapshot.data()));
data = documentSnapshot.data();
});
});
this.page = data;
And it show the JSON I am expecting in the console, but it does not change the UI. It still gives the data not defined error.
Update 2
fetchData () {
console.log("Getting content for: " + this.id);
let data = null;
db.collection('pages')
.where("url", "==", this.id)
.get()
.then(function(querySnapshot) {
querySnapshot.forEach(function (documentSnapshot) {
data = documentSnapshot.data();
});
});
this.page = data;
console.log(JSON.stringify(this.page));
}
Now gets. It failing on initial load and follow up loads too.
EDIT
Your problem is with asynchronous loading. You're making an async call to db.collection() but updating your this.page in viewModel after making db.collection() call. So this is why your this.page is not ending up with the data you're desiring to be updated with. Try this & I think it'll work:
fetchData () {
console.log("Getting content for: " + this.id);
db.collection('pages')
.where("url", "==", this.id)
.get()
.then(querySnapshot => {
querySnapshot.forEach(documentSnapshot => {
this.page = documentSnapshot.data(); //this.page will hold last documentSnapshot data
});
});
}
}
It looks like a const is what was needed needed.
fetchData () {
console.log("Getting content for: " + this.id);
db.collection('pages')
.where("url", "==", this.id)
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
const {title, content} = doc.data();
this.title = title;
this.content = content;
});
});
console.log(this.title);
}
Related
I have a Vue application. I would like to retrieve entries (from a database) based on a userid. I have the following methods in Vue:
export default {
name: 'Entries',
data() {
return {
userid: null
};
},
methods: {
getEntries() {
this.getUserID();
console.log("userid getEntries: " + this.userid);
axios.get('/entries', this.userid)
.then((res) => {
this.entries = res.data;
console.log(this.entries);
})
.catch((error) => {
console.error(error);
});
},
getUserID() {
axios.get('/userid')
.then((res) => {
this.userid = res.data;
console.log("userid getUserId: " + this.userid );
})
.catch((error) => {
console.error(error);
});
},
},
created() {
this.getEntries();
}
};
Within the getEntries method, I'm immediately calling the getUserID function. I would assume this sets the variable userid to the value retrieved from the getUserID method.
Instead I get the following output in the browser console, in exactly this order:
userid getEntries: null
userid getUserId: user_a
Why does it print first the console output from the getEntries function? And why is this null if it first executes the getUserID method?
How could I change is so that the axios call /entries can pass the userid.
axios calls are async, if you need the userid populated before anything else is called, then you should populate before anything else is called, in mounted/created.
Then you can react to its change with a watcher. You could call getEntries when the getUserID call resolves but it's dirty and couples the two methods.
And don't forget to assign entries in data.
This will work:
export default {
name: 'Entries',
data() {
return {
userid: null,
entries: []
};
},
watch: {
userid (v) {
if (v) this.getEntries()
}
},
mounted() {
this.$nextTick(this.getUserID)
},
methods: {
getEntries() {
console.log("userid getEntries: " + this.userid);
axios.get('/entries', this.userid)
.then((res) => {
this.entries = res.data;
console.log(this.entries);
})
.catch((error) => {
console.error(error);
});
},
getUserID() {
axios.get('/userid')
.then((res) => {
this.userid = res.data;
console.log("userid getUserId: " + this.userid);
})
.catch((error) => {
console.error(error);
});
}
}
};
Using async/await
export default {
name: 'Entries',
data() {
return {
userid: null,
entries: []
};
},
watch: {
userid (v) {
if (v) this.getEntries()
}
},
mounted() {
this.$nextTick(this.getUserID)
},
methods: {
async getEntries() {
try {
const { data } = await axios.get('/entries', this.userid)
this.entries = data;
} catch (error) {
console.error(error);
}
},
async getUserID() {
try {
const { data } = await axios.get('/userid')
this.userid = data;
} catch (error) {
console.error(error);
}
}
}
};
Since getUserId is asynchronous, it doesn't return immediately. You therefore need to wait for it to return before continuing. This can be done using then but, nested Promises are a bit unpleasant to work with. A simpler option is to use async/await instead:
async getEntries() {
await this.getUserID();
console.log("userid getEntries: " + this.userid);
axios.get('/entries', this.userid)
.then((res) => {
this.entries = res.data;
console.log(this.entries);
})
.catch((error) => {
console.error(error);
});
},
I want to make several API calls to get data into a component. I created a PostService.ts that looks like this:
const apiClient = axios.create({
baseURL: '/api/v1',
})
export default {
async getPosts() {
const { data }: { data: Post[] } = await apiClient.get('/posts')
// transform data ...
return data
},
async getTags() {
const { data }: { data: Tag[] } = await apiClient.get('/tags')
return data
},
async getComments() {
const { data }: { data: Comment[] } = await apiClient.get('/comments')
return data
},
}
This is my posts.vue:
<template>
<div>
<div v-if="dataLoaded">
content
</div>
<div v-else>
loading...
</div>
</div>
</template>
<script>
finishedApiCalls = 0
get dataLoaded() {
return this.finishedApiCalls === 3
}
created() {
PostService.getPosts()
.then((posts) => {
this.posts = posts
this.finishedApiCalls++
})
.catch((error) => {
console.log('There was an error:', error)
})
PostService.getTags()
.then((tags) => {
this.tags = tags
this.finishedApiCalls++
})
.catch((error) => {
console.log('There was an error:', error)
})
PostService.getComments()
.then((comments) => {
this.comments = comments
this.finishedApiCalls++
})
.catch((error) => {
console.log('There was an error:', error)
})
}
</script>
The key point is that I want to display a loading spinner as long as the data has not been loaded. Is it recommended to make the API calls from created()? What would be a more elegant way to find out when all calls are finished? It does not feel right to use the finishedApiCalls variable.
I recommend using Nuxt's fetch method along with Promise.all() on all your async PostService fetches:
// MyComponent.vue
export default {
fetch() {
return Promise.all([
PostService.getPosts().then((posts) => ...).catch((error) => ...),
PostService.getTags().then((tags) => ...).catch((error) => ...),
PostService.getComments().then((comments) => ...).catch((error) => ...)
])
}
}
Nuxt provides a $fetchState.pending prop that you could use for conditionally rendering a loader:
<template>
<div>
<Loading v-if="$fetchState.pending" />
<div v-else>My component data<div>
</div>
</template>
You can use Promise.all for this kind of requirements.
this.loading = true
Promise.all([PostService.getPosts(), PostService.getTags(), PostService.getComments()])
.then(values => {
let [posts, tags, comments] = values
this.posts = posts
this.tags = tags
this.comments = comments
//Here you can toggle your fetching flag like below
this.loading = false
})
You can use Promise.all(). This will wait till all resolves or if 1 fails.
With async / await you can make it "synchronous"
data() {
return {
loaded: false
}
},
async created() {
let [posts, tags, comments] = await Promise.all([PostService.getPosts(), PostService.getTags(), PostService.getComments()])
this.posts = posts;
this.tags = tags;
this.comments = comments;
this.loaded = true;
}
I'm a new VueJS user, currently struggling with updating image src on the fly. This is what I've got:
Template:
<div v-for="place in places">
<img
v-bind:src="isPlacePrivate(place.data.place_is_private)"
v-on:click="setPlaceAsPrivate(place.data.place_is_private, place.data.place_id)"
>
</div>
<script>
export default {
data: function () {
return {
places: null,
}
},
mounted () {
this.username = this.$route.params.username;
axios.get('/api/' + this.username + '/places')
.then(response => {
this.places = response.data.data;
})
.catch(error => {
// show error
});
},
methods: {
isPlacePrivate: function (value) {
// If Place is private
if (value == 1) {
var src = '/icons/padlock-color.png'
} else {
var src = '/icons/padlock.png'
}
return src;
},
setPlaceAsPrivate: function (value, placeId) {
let data = {
isPrivate: value
};
axios.put('/api/' + this.username + '/places/' + placeId + '/edit', data)
.then(response => {
let newValue = response.data.data.private;
this.isPlacePrivate(newValue);
})
.catch(error => {
// show error
});
},
},
}
</script>
On a page load -> if a particular place is private it will show colored padlock icon or uncolored padlock if a place is public!
A user will be able to press on the padlock icon and change the value from public->private or private->public.
Everything is working fine but the padlock image is not updating on the fly when a user is clicking on it, I need to refresh a page to see changes! How to make it work?
I would suggest using a computed property so that it is reactive
Also according to your updates you are looping through an array of places so when you get your response from your axios call instead of just updating the icon I would try replacing the object in the array so I created the method called updatePlace() and I pass in the response object.
And change your places in the v-for to a computed property as well so that it is also reactive
Template:
<div v-for="place in placesArray" :key="index" v-if="places">
<img
v-bind:src="imgSrc"
v-on:click="setPlaceAsPrivate(place.data.place_is_private, place.data.place_id)"
v-if="imgSrc"
>
</div>
Script:
<script>
export default {
data() {
return {
src: '',
places: null
}
},
computed: {
imgSrc() {
return this.src
},
placesArray() {
return this.places
}
},
Methods: {
isPlacePrivate: function (value) {
// If Place is private
if (value == 1) {
this.src = '/icons/padlock-color.png'
} else {
this.src = '/icons/padlock.png'
}
},
setPlaceAsPrivate: function (value, placeId) {
let data = {
isPrivate: value
};
axios.put('/api/' + this.username + '/places/' + placeId + '/edit', data)
.then(response => {
console.log(response);
let newValue = response.data.data;
this.updatePlace(newValue);
})
.catch(error => {
console.log(error);
});
},
},
updatePlace(newPlace) {
const index = this.places.findIndex(place => place.id === newPlace.id)
this.places.splice(index, 1, place)
},
created() {
this.username = this.$route.params.username;
axios.get('/api/' + this.username + '/places')
.then(response => {
this.places = response.data.data;
})
.catch(error => {
// show error
});
}
}
</script>
Also make sure to move your mounted method to a created() method so that it is called before anything else is trying to render.
Apparently the problem is that you are calling the function and printing its return on the <img v-bind:src>, the isPlacePrivate function returns a value, so when you use this function within the setPlaceAsPrivate it returns the value only in scope of setPlaceAsPrivate.
The isPlacePrivate function does not modify any data value of the component, so the image always remains the same. You just need to set a data and manipulate its value in the isPlacePrivate function.
Template
<img
v-bind:src="bindSrc"
v-on:click="setPlaceAsPrivate(place.data.place_is_private, place.data.place_id)"
>
Script
<script>
export default {
data() {
return {
bindSrc: '/icons/padlock-color.png', // default img src value
... // your other values
}
},
Methods: {
isPlacePrivate: function (value) {
// If Place is private
if (value == 1) {
this.bindSrc = '/icons/padlock-color.png'
} else {
this.bindSrc = '/icons/padlock.png'
}
},
setPlaceAsPrivate: function (value, placeId) {
let data = {
isPrivate: value
};
axios.put('/api/' + this.username + '/places/' + placeId + '/edit', data)
.then(response => {
console.log(response);
let newValue = response.data.data.private;
this.isPlacePrivate(newValue);
})
.catch(error => {
console.log(error);
});
},
}
}
</script>
Vue.component('test', {
template: `some html`,
data() {
{
return {
somedata: 'hey, starting!'
}
}
},
methods: {
fetchdata: function fetchdata() {
fetch('http://localhost:5000/getmesome')
.then(response => response.json()).then(data => this.somedata = data
);
}
}, created() {
this.fetchdata();
console.log(this.somedata); //returns 'hey starting' not the fetched data.
}
});
As shown in the code comment, this is not refreshing the property with the fetched data. How can I do it?
Thanks.
fetchdata() will return immediately while the request is still in progress since it is an async operation. console.log(this.somedata) will be executed before the fetch operation has completed.
This is a basic async misunderstanding; I would suggest you read up on asynchronous JavaScript topics (promises, async and await, etc).
Either of these solutions will work:
methods: {
fetchdata() {
return fetch('http://localhost:5000/getmesome')
.then(response => response.json())
.then(data => this.somedata = data);
}
},
created() {
this.fetchdata()
.then(() => console.log(this.somedata));
}
methods: {
async fetchdata() {
const res = await fetch('http://localhost:5000/getmesome');
const data = await res.json();
this.somedata = data;
}
},
async created() {
await this.fetchdata();
console.log(this.somedata);
}
I'm having an issue where the store data "menus" is not updated after i do a login.
Appearantly.. the object "loggedInUser" is not sat before i call "getMenus".. I'm not sure what i'm doing wrong here...
PS! When debugging in chrome, i notice that loggedInUser is "null" when entering the api call (see api.js codesnippet).
Login.vue (method) :
methods: {
doLogin() {
this.errorMessage = '';
this.loading = true;
let userCredentials = {
'username': this.loginEmail,
'password': this.loginPassword
};
this.$store.dispatch('tryLogin', {
'login': this.loginEmail,
'password': this.loginPassword
}).then((response) => {
this.$store.dispatch('getMenus')
.then((response) => {
this.$router.push('/')
});
});
}
},
Menus.vue (same as /)
computed: {
menus() {
return this.$store.getters.menus
}
},
created() {
this.$store.dispatch('getMenus')
},
methods: {
viewMenu: function(item) {
console.log("=> View Menu : " + item.Name)
this.$router.push('/viewmenu/' + item.Id)
}
}
}
store.js (getMenus action AND tryLogin)
actions: {
getMenus({ commit, getters }) {
api.getMenus(getters.loggedInUser)
.then(menus => {
commit('UPDATE_MENUS', menus);
});
},
tryLogin({ commit }, credentials) {
api.tryLogin(credentials)
.then(loggedInUser => {
commit('LOGGED_IN_USER', loggedInUser);
});
},
api.js (getMenus function)
getMenus(loggedInUser) {
var hostname = 'http://myurl'
var config = {
headers: {
'Content-Type': 'application/json'
}
}
var endpointUrl = hostname + '/api/Menu/GetMenus';
if (loggedInUser != null){
endpointUrl = hostname + '/api/Menu/GetMenusForSubCompany/' + loggedInUser.encryptedsubcompanyid;
}
return axios.get(endpointUrl, config)
.then(response => response.data);
},
From your store.js snippet, it seems you forget to return the promise.