VueJS: Fetch data before and after loading a component - javascript

I am new to VueJS and working on a component and want to fetch some data from an API before the corresponding route is loaded; only then the component should load. Once the component is created, I have to call another API that takes as input the data obtained from first API. Here is my component script:
export default {
name: 'login',
data () {
return {
categories: []
}
},
created () {
// it gives length = 0 but it should have been categories.length
console.log(this.categories.length);
// Call getImage method
loginService.getImage(this.categories.length)
.then(res => {
console.log('Images fetched');
})
},
beforeRouteEnter (to, from, next) {
loginService.getCategories().then((res) => {
next(vm => {
vm.categories = res.data.categories;
});
}, (error) => {
console.log('Error: ', error);
next(error);
})
},
methods: {}
}
I tried using mounted hook but it does not work. However if I watch the categories property and call fetch image method, it works. I don't think using watchers is the best approach here. Any thoughts?

Move your call to get additional information into a method and call that method from next.
export default {
name: 'login',
data () {
return {
categories: []
}
},
beforeRouteEnter (to, from, next) {
loginService.getCategories().then((res) => {
next(vm => {
vm.categories = res.data.categories;
vm.getMoreStuff()
});
}, (error) => {
console.log('Error: ', error);
next(error);
})
},
methods: {
getMoreStuff(){
console.log(this.categories.length);
// Call getImage method
loginService.getImage(this.categories.length)
.then(res => {
console.log('Images fetched');
})
}
}
}
Alternatively, do it in the callback for getCategories.
loginService.getCategories()
.then(res => {
vm.categories = res.data.categories;
loginService.getImage(vm.categories.length)
.then(res => //handle images then call next())
})
.catch(err => //handle err)
Or if you are using a pre-compiler that handles async/await
async beforeRouteEnter(to, from, next){
try{
const categoryResponse = await loginService.getCategories()
const categories = categoryResponse.data.categories
const imageResponse= await loginService.getImage(categories.length)
next(vm => {
vm.categories = categories
vm.images = imageResponse.data.images
})
} catch(err){
//handle err
}
}

Related

Call Firebase Function in javascript

I have a Cloud Function deployed to Firebase, and my iOS and Android apps use it fine, all works good. Below is the function deployed.
const admin = require('firebase-admin');
const firebase_tools = require('firebase-tools');
const functions = require('firebase-functions');
admin.initializeApp();
exports.deleteUser = functions
.runWith({
timeoutSeconds: 540,
memory: '2GB'
})
.https.onCall((data, context) => {
const userId = context.auth.uid;
var promises = [];
// DELETE DATA
var paths = ['users/' + userId, 'messages/' + userId, 'chat/' + userId, 'like/' + userId];
paths.forEach((path) => {
promises.push(
recursiveDelete(path).then( () => {
return 'success';
}
).catch( (error) => {
console.log('Error deleting user data: ', error);
})
);
});
// DELETE FILES
const bucket = admin.storage().bucket();
var image_paths = ["avatar/" + userId, "avatar2/" + userId, "avatar3/" + userId];
image_paths.forEach((path) => {
promises.push(
bucket.file(path).delete().then( () => {
return 'success';
}
).catch( (error) => {
console.log('Error deleting user data: ', error);
})
);
});
// DELETE USER
promises.push(
admin.auth().deleteUser(userId)
.then( () => {
console.log('Successfully deleted user');
return true;
})
.catch((error) => {
console.log('Error deleting user:', error);
})
);
return Promise.all(promises).then(() => {
return true;
}).catch(er => {
console.error('...', er);
});
});
function recursiveDelete(path, context) {
return firebase_tools.firestore
.delete(path, {
project: process.env.GCLOUD_PROJECT,
recursive: true,
yes: true,
token: functions.config().fb.token
})
.then(() => {
return {
path: path
}
}).catch( (error) => {
console.log('error: ', error);
return error;
});
}
// [END recursive_delete_function]
How can I execute this script with a button in javascript? A standard .js file locally? I also need to be able to pass in a userId manually.
In my react native app I call it like:
const deleteUser = async () => {
functions().httpsCallable('deleteUser')()
signOut();
}
But in my javascript file (nothing to do with my react native app), I need to pass in a userId and call that same function to delete the user.
There are a number of ways to go about executing a cloud function within your client side application.
Depending on how you have the function setup, you can either pass in a parameter or data via the body in the request.
For example, using express (similar to other frameworks):
// fetch(‘api.com/user/foo’, {method: ‘DELETE’} )
app.delete(‘/user/:uid’, (req, res) => {
const uid = req.params.uid;
// execute function
})
// fetch(‘api.com/user’, {method: ‘DELETE’, body: { uid: foo } } )
app.delete(‘/user’, (req, res) => {
const uid = req.body.uid;
// execute function
})
// fetch(‘api.com/user?uid=foo’, {method: ‘DELETE’} )
app.delete(‘/user’, (req, res) => {
const uid = req.query.uid;
// execute function
})
Full Example:
<button onclick=“deleteUser(uid)”>Delete Me</button>
<script>
function deleteUser(uid) {
fetch(`api.com/user/${uid}`, { method: ‘DELETE’});
// rest of function
}
</script>
Was able to call my firebase function with the following:
userId was accessible like so const { userId } = data; from my function script
async function deleteAccount(userId) {
const deleteUser = firebase.functions().httpsCallable("deleteUser");
deleteUser({ userId }).then((result) => {
console.log(result.data);
});
}

get route is correct but api is still not working(fetching nothing)

I am trying to make a get route for this API:
https://api.nasa.gov/mars-photos/api/v1/rovers/opportunity/photos?sol=1000&api_key=92Ll6nGuQhfGjZnT2gxaUgiBhlCJ9K1zi2Fv5ONn
And although the syntax for the get route, the API still doesn't work in postman nor in client-side.
Here's the get route code:
app.get('/roverInfo/:rover_name', async (req, res) => {
const { rover_name } = req.params
try {
let images = await fetch(`https://api.nasa.gov/mars-photos/api/v1/rovers/${rover_name}/photos?sol=1000&api_key=${process.env.API_KEY}`).then((res) => res.json())
res.send({ images })
} catch (err) {
console.log('error:', err)
}
})
sandbox here
and here's the client-side request:
const showRovers = async (rovers) => {
try {
await fetch(`https://localhost:3000/roverInfo/:rover_name`)
.then((res) => {
return res.json()
})
.then((rovers) => updateStore(store, { rovers }), console.log(rovers))
} catch (error) {
console.log('errors:', error)
}
}
and here's the error I am getting:
Failed to load resource: net::ERR_SSL_PROTOCOL_ERROR
ADVISE: Don't mix await/async with .then, use either one
app.get("/roverInfo/:rover_name", async (req, res) => {
const { rover_name } = req.params;
try {
const res = await fetch(
`https://api.nasa.gov/mars-photos/api/v1/rovers/${rover_name}/photos?sol=1000&api_key=${process.env.API_KEY}`
) // removed .then
const images = await res.json(); // await response to json
res.send({ images });
} catch (err) {
console.log("error:", err);
}
});
02. should be http instead of https
03. need to pass rover name to param instead of using :rover_name
let getRovers = showRovers('opportunity');
const showRovers = async (roverName) => {
try {
console.log("roverName", roverName)
// use http here
await fetch(`http://localhost:3000/roverInfo/${roverName}`)
.then((res) => {
return res.json();
})
.then((rovers) => updateStore(store, { rovers }));
} catch (error) {
console.log("errors:", error);
}
};

Reuse same view and logic just change the endpoints in VUE

I came to an point where i have a bunch of endpoints that behave the same like:
http:://api.development/projects/status/types
http:://api.development/projects/errors/types
http:://api.development/projects/priority/types
They all have the same verbs: GET, POST (add), PUT (edit) and DELETE and they share the same data structure:
{
name: "",
description: ""
}
Therefore the view and the logic to manage that on my client will be the same.
I am using VUE for the client. I thought of creating a component to reuse the view and create three other components that includes that component. Therefore the logic will be written in all of this three parent components.
But how can I implement the logic to reuse it across them? The only piece of code will change will be the endpoints.
You can create a service file, and there you declare your API calls, and then just export that file as a component and reuse the calls anywhere in your app.
It would look something like this:
import axios from 'axios'
const api = 'API'
export default {
data() {
return {
user: `${api}/some/route`,
hotels: `${api}/other/route/`
}
},
methods: {
getHeaders() {
return {
headers: {
Authorization: 'Bearer ' + 'TOKEN',
'Content-Type': 'application/json'
}
}
},
getModule(route, cb) {
axios
.get(route, this.getHeaders())
.then(response => {
cb(response.data)
})
.catch(err => {
cb(err)
})
},
postModule(route, data, cb) {
axios
.post(route, data, this.getHeaders())
.then(response => {
cb(response.data)
})
.catch(e => {
cb(e)
})
},
putModule(route, data, cb) {
axios
.put(route, data, this.getHeaders())
.then(response => {
cb(response.data)
})
.catch(e => {
console.log(e)
})
},
deleteModule(route, id, cb) {
axios
.delete(route + id, this.getHeaders())
.then(response => {
cb(response.data)
})
.catch(e => {
console.log(e)
})
}
}
}
In the component where you want to execute the call you do this:
import ServiceFileName from '#/services/YourServiceFileName'
methods:{
getData () {
Main.methods.getModule(Main.data().hotels, data => {
console.log(data)
})
}
}
You mean something like mixins?

Axios ajax, show loading when making ajax request

I'm currently building a vue app and Im using axios. I have a loading icon which i show before making each call and hide after.
Im just wondering if there is a way to do this globally so I dont have to write the show/hide loading icon on every call?
This is the code I have right now:
context.dispatch('loading', true, {root: true});
axios.post(url,data).then((response) => {
// some code
context.dispatch('loading', false, {root: true});
}).catch(function (error) {
// some code
context.dispatch('loading', false, {root: true});color: 'error'});
});
I have seen on the axios docs there are "interceptors" but II dont know if they are at a global level or on each call.
I also saw this post for a jquery solution, not sure how to implement it on vue though:
$('#loading-image').bind('ajaxStart', function(){
$(this).show();
}).bind('ajaxStop', function(){
$(this).hide();
});
I would setup Axios interceptors in the root component's created lifecycle hook (e.g. App.vue):
created() {
axios.interceptors.request.use((config) => {
// trigger 'loading=true' event here
return config;
}, (error) => {
// trigger 'loading=false' event here
return Promise.reject(error);
});
axios.interceptors.response.use((response) => {
// trigger 'loading=false' event here
return response;
}, (error) => {
// trigger 'loading=false' event here
return Promise.reject(error);
});
}
Since you could have multiple concurrent Axios requests, each with different response times, you'd have to track the request count to properly manage the global loading state (increment on each request, decrement when each request resolves, and clear the loading state when count reaches 0):
data() {
return {
refCount: 0,
isLoading: false
}
},
methods: {
setLoading(isLoading) {
if (isLoading) {
this.refCount++;
this.isLoading = true;
} else if (this.refCount > 0) {
this.refCount--;
this.isLoading = (this.refCount > 0);
}
}
}
demo
I think you are on the right path with dispatch event when ajax call start and finish.
The way that I think you can go about it is to intercept the XMLHttpRequest call using axios interceptors like so:
axios.interceptors.request.use(function(config) {
// Do something before request is sent
console.log('Start Ajax Call');
return config;
}, function(error) {
// Do something with request error
console.log('Error');
return Promise.reject(error);
});
axios.interceptors.response.use(function(response) {
// Do something with response data
console.log('Done with Ajax call');
return response;
}, function(error) {
// Do something with response error
console.log('Error fetching the data');
return Promise.reject(error);
});
function getData() {
const url = 'https://jsonplaceholder.typicode.com/posts/1';
axios.get(url).then((data) => console.log('REQUEST DATA'));
}
function failToGetData() {
const url = 'https://bad_url.com';
axios.get(url).then((data) => console.log('REQUEST DATA'));
}
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<button onclick="getData()">Get Data</button>
<button onclick="failToGetData()">Error</button>
For Nuxt with $axios plugin
modules: ['#nuxtjs/axios', ...]
plugins/axios.js
export default ({ app, $axios ,store }) => {
const token = app.$cookies.get("token")
if (token) {
$axios.defaults.headers.common.Authorization = "Token " + token
}
$axios.interceptors.request.use((config) => {
store.commit("SET_DATA", { data:true, id: "loading" });
return config;
}, (error) => {
return Promise.reject(error);
});
$axios.interceptors.response.use((response) => {
store.commit("SET_DATA", { data:false, id: "loading" });
return response;
}, (error) => {
return Promise.reject(error);
})
}
store/index.js
export default {
state: () => ({
loading: false
}),
mutations: {
SET_DATA(state, { id, data }) {
state[id] = data
}
},
actions: {
async nuxtServerInit({ dispatch, commit }, { app, req , redirect }) {
const token = app.$cookies.get("token")
if (token) {
this.$axios.defaults.headers.common.Authorization = "Token " + token
}
let status = await dispatch("authentication/checkUser", { token })
if(!status) redirect('/aut/login')
}
}
}
This example is accompanied by a token check with $axios and store

VueRouter wait for ajax is done

I am building SPA and the problem is checking if user is admin or not.
After Vue.auth.getUserInfo() I want to stop whole application and wait for API response, Vue.auth.user.isAdmin is always false because I don't have response from api...
Here is router.beforeEach
router.beforeEach((to, from, next) => {
if(Vue.auth.user.authenticated == false) {
Vue.auth.getUserInfo();
}
if(Vue.auth.user.isAdmin) {
next({ name: 'admin.index' })
} else {
next({name: 'client.index'})
}
}
Get user info method:
getUserInfo() {
Vue.http.get('/api/me')
.then(({data}) => {
this.user = data;
}, () => {
this.logout();
})
}
Assuming the state of Vue.auth.user.isAdmin is managed within your Vue.auth.getUserInfo() logic, you can try a promise approach (untested):
getUserInfo() {
return new Promise((resolve, reject) => {
Vue.http.get('/api/me')
.then(({data}) => {
this.user = data;
// Or, to use when consuming this within the then() method:
resolve(data);
}, () => {
reject();
})
})
}
Then, when you consume it in your guard (https://router.vuejs.org/en/advanced/navigation-guards.html):
// A couple small auth/guard helper functions
function guardCheck(next) {
if(Vue.auth.user.isAdmin) {
next({ name: 'admin.index' })
} else {
next({name: 'client.index'})
}
}
function guardLogout(next) {
Vue.auth.user.logout()
.then(() => {
next({ name: 'home.index', params: { logout: success }})
})
}
router.beforeEach((to, from, next) => {
if(Vue.auth.user.authenticated === false && !to.matched.some(record => record.meta.isGuest)) {
Vue.auth.getUserInfo()
.then((user) => {
guardCheck(next)
})
.catch(() => {
// Not sure how your logout logic works but maybe...
guardLogout(next)
})
} else {
guardCheck(next)
}
}
It is asynchronus request.
You have few options.
1. Move this function to vue-router and place your code:
if(Vue.auth.user.authenticated == false) {
Vue.auth.getUserInfo();
}
if(Vue.auth.user.isAdmin) {
next({ name: 'admin.index' })
} else {
next({name: 'client.index'})
}
}
in then() function of your request.
Probably better for your learning curve - to modify your getUserInfo() to be promise based.
You will then have in your auth module something like:
var getUserInfo = new Promise((resolve,reject) => {
Vue.http.get('/api/me')
.then(({data}) => {
this.user = data;
resolve();
}, () => {
this.logout()
reject();
})
}
and in your router:
router.beforeEach((to, from, next) => {
if(Vue.auth.user.authenticated == false) {
Vue.auth.getUserInfo().then(()=>{
if(Vue.auth.user.isAdmin) {
next({ name: 'admin.index' })
} else {
next({name: 'client.index'})
}
});
}
}
I don't have an editor with me so it can have some small issues but generally should work. Hope it helps!

Categories

Resources