How to wait for ajax call from main Vue instance? - javascript

I currently have VueJS components that makes an ajax call to github like so:
(Child) component
Vue.http.get('user/repos').then((response) => {
console.log(response);
}, (response) => {
console.log(response);
});
The problem is that I first need to get an access token before I can make this ajax call. This access token is stored in the database so my main Vue component is making the ajax call to set a common header to all ajax calls:
Main Vue instance
Vue.http.headers.common['Authorization'] = `token ${this.token}`;
const app = new Vue({
el: '#app',
data: {
token: ''
},
created() {
Vue.http.get('/token').then((response) => {
this.token = response.data.token;
}, () => {
console.log('failed to retrieve the access token for the logged in user.');
})
}
});
How can I be sure that before running the ajax call from my component that the ajax call to set the 'Authorization' header has been successful?

Adding this for anyone else who could benefit.
Get the token from the API call, add it up in a vuex state variable.
Access the same using a getter in the child component, as a computed property or you can pass it on as props or via an event bus but both the ways are not as powerful as using vuex.
watch over the property, and perform your required action when the token is obtained.
// Add this up in the child component
computed: {
...mapGetters({
token: <name-of-the-getter> // token becomes the alias for the computed
}) // property.
},
watch: {
token () {
if(this.token) this.someAPICall()// or some other applicable condition
}
},
methods: {
...mapActions({
someAPICall: <name-of-the-action>
})
}
// ----------------------------------------------
Watch requires the value to change, I have noticed that commits made in an action cause the watch to trigger. So if for some reason the token is lost, or expires you will naturally not be able to make the subsequent requests.
EDIT
import store from 'path/to/store'
axios.interceptors.response.use(function (response) {
// extract the token from the response object
// save the token to the store for access during subsequent
// requests.
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});
axios.interceptors.request.use(function (config) {
// use store getters to access token
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});

You can replace/proxyfiy Vue.http.get function by your own function that will request token first and then do your request, rough idea:
!function()
{
var vue_http_get = Vue.http.get;
var token = null;
// patching/proxying Vue.http.get function
Vue.http.get = function get() {
vue_http_get.apply(Vue.http,"path/to/get/token/").then(function(resp){
token = resp;
Vue.http.headers.common['Authorization'] = ...;
// putting back original Vue.http
Vue.http = vue_http_get;
return Vue.http.get(arguments[0]);
});
};
}();

Related

Why my first load data axios is return 404 Vue Js?

so guys I've tried to fetch data and show it inside my component. But the problem is, fetching data got 404 or data not show for the first load.. but when I try to reload again data is shown as should be. And one thing.. the data was success upload to the server even the response status 404 as I said
By the way guys this is my component.js
getApi() {
return api.get("/routeName")
}
this is my Store
async fetchApi({ commit }) {
try {
let {
data: { data }
} = await component.getApi()
commit("SET_API", data)
} catch (error) {
return Promise.reject(error)
}
}
and this is how I call fetchApi from a store inside my component
async created() {
await this.getApi()
}
methods: {
async getDraft() {
try {
await this.$store.dispatch("component/fetchApi")
this.scrollToTop()
} catch (error) {
error
}
}
}
I assume you access VUEX state data in <template> tag (I cannot comment to get more information.)
If I right, the problem is compoent not watch VUEX state when data null while you access.
If you render when VUEX state data is null, when data update while you call fetchDraft in yours store. In component not track your VUEX. I don't know why it happen in low level.
In my case, I need to use setInterval to load Vuex state to local data in component. And use it instead of access direct to vuex.
ex.
created() {
this.intervalUpdate = setInterval(() => {
this.updateData();
}, 1000);
},
destroyed() {
clearInterval(this.intervalUpdate);
},
methods: {
async updateData() {
this.draftData = await this.$store.dispatch("review/fetchDraft");
}
}
It's not the best solution. But its help me to solve problem that component

Managing two states in one action in Vuex

I have two APIs reporting two sets of data (lockboxes and workstations). The lockboxes API has a collection of agencies with a recordId that I need to manipulate. The workstations API is the main collection that will assign one of these agencies (lockboxes) on a toggle to a workstation by sending the lockboxes.recordId and the workstation.recordId in the body to the backend.
My store looks like this
import { axiosInstance } from "boot/axios";
export default {
state: {
lockboxes: [],
workstation: []
},
getters: {
allLockboxes: state => {
return state.lockboxes;
},
singleWorkstation: state => {
let result = {
...state.workstation,
...state.lockboxes
};
return result;
}
},
actions: {
async fetchLockboxes({ commit }) {
const response = await axiosInstance.get("agency/subagency");
commit("setLockboxes", response.data.data);
},
updateAgency: ({ commit, state }, { workstation, lockboxes }) => {
const postdata = {
recordId: state.workstation.recordId,
agency: state.lockboxes.recordId
};
return new Promise((resolve, reject) => {
axiosInstance
.post("Workstation/update", postdata)
.then(({ data, status }) => {
if (status === 200) {
resolve(true);
commit("setWorkstation", data.data);
commit("assignAgency", workstation);
console.log(state);
}
})
.catch(({ error }) => {
reject(error);
});
});
}
},
mutations: {
setWorkstation: (state, workstation) => (state.workstation = workstation),
assignAgency(workstation) { workstation.assign = !workstation.assign},
setLockboxes: (state, lockboxes) => (state.lockboxes = lockboxes)
}
};
Process:
When I select a lockbox from the dropdown and select a toggle switch in the workstation that I want to assign the lockbox too, I do get the lockbox to show but it goes away on refresh because the change only happened on the front end. I'm not really passing the workstation.recordId or lockboxes.recordId in my body as I hoped I was. It is not reading the state and recognizing the recordId for either state(workstation or lockboxes).
the console.log is returning (Uncaught (in promise) undefined)
The request is 404ing with an empty Payload in the body ( {} )
Not even the mutation is firing
template
toggleAssign(workstation) {
this.updateAgency(workstation);
}
At some point I had it that is was reading the workstation.recordId before I tried to merge the two states in the getter but I was never able to access the lockboxes.recordId. How can I have access to two states that live in two independent APIs so I can pass those values in the body of the request?
You can add debugger; in your code instead of console.log to create a breakpoint, and inspect everything in your browser's debug tools.
I can't really help because there are very confusing things:
state: {
lockboxes: [],
workstation: []
},
So both are arrays.
But then:
setWorkstation: (state, workstation) => (state.workstation = workstation),
assignAgency(workstation) { workstation.assign = !workstation.assign},
It seems that workstation is not an array?
And also this, in the getters:
singleWorkstation: state => {
let result = {
...state.workstation,
...state.lockboxes
};
return result;
}
I'm not understanding this. You're creating an object by ...ing arrays? Maybe you meant to do something like:
singleWorkstation: state => {
let result = {
...state.workstation,
lockboxes: [...state.lockboxes]
};
return result;
}
Unless lockboxes is not an array? But it's named like an array, it's declared as an array. You do have this however:
const postdata = {
recordId: state.workstation.recordId,
agency: state.lockboxes.recordId
};
So it seems it's not an array?
Finally, in your updageAgency method, and this is where the problem may lie:
return new Promise((resolve, reject) => {
axiosInstance
.post("Workstation/update", postdata)
.then(({ data, status }) => {
if (status === 200) {
resolve(true);
commit("setWorkstation", data.data);
commit("assignAgency", workstation);
console.log(state);
}
})
.catch(({ error }) => {
reject(error);
});
});
The .then first arg of axios is only invoked if the status code is 2xx or 3xx. So your test if (status === 200) is superfluous because errors would not get there. And if for a reason of another you have a valid code other than 200, the promise never ends. reject is never called, as it's not an error, and neither is resolve. So you should remove check on the status code.
You should also call resolve(true) after the two commits, not before...
Finally your mutation assignAgency is declared all wrong:
assignAgency(workstation) { workstation.assign = !workstation.assign},
A mutation always takes the state as the first param. So it should either be:
assignAgency(state, workstation) {
state.workstation = {...workstation, assign: !workstation.assign}
},
Or
assignAgency(state) {
state.workstation = {...state.workstation, assign: !state.workstation.assign}
},
Depending on if you even need the workstation argument, given that what you want is just toggle a boolean inside an object.
TLDR: I'm not sure if lockboxes should be an array or an object, remove the status check inside your axios callback, fix the assignAgency mutation, use breakpoints with debugger; and the VueJS chrome plugin to help examine your store during development.
In an action, you get passed 2 objects
async myAction(store, payload)
the store object is the whole vuex store as it is right now. So where you are getting commit, you can get the state like so
async fetchLockboxes({ commit,state }) {//...}
Then you can access all state in the app.
You may use rootState to get/set whole state.
updateAgency: ({ commit, rootState , state }, { workstation, lockboxes }) {
rootState.lockboxes=[anything you can set ]
}

Pass parameter/argument to axios interceptor

How do I send custom parameters to the axios interceptor? I am using an interceptor like this:
window.axios.interceptors.request.use(function (config) {
if (PASSED_PARAM == true) {
doSomethingAwesome();
}
return config;
}, function (error) {
return Promise.reject(error);
});
I also have a response interceptor that needs to receive the same parameter.
Merge params
axios.interceptors.request.use((config) => {
config.params = {...config.params, my_variable: 'value'}
return config
})
The method suggested by #Laurent will cause axios to wipe out all your other parameters and replace it with my_variable, which is may not exactly what you want.
The proper way of adding default parameters instead of replacing it is like this:
axios.defaults.params = {};
axios.interceptors.request.use(function (config) {
config.params['blah-defaut-param'] = 'blah-blah-default-value';
return config;
}, function (error) {
return Promise.reject(error);
});
This works with axios 0.18.1. It does not work with axios 0.19 due to a regression bug..., I believe it will be fixed soon.
The accepted answer, and the answers on this page seem to have missed what the question is asking.
This question is asking something like "When I call axios can I pass data to the interceptor but not to the server" and the answer is yes. Though it is undocumented and when using typescript you'll have to add a //#ts-ignore.
When you call axios you can pass a config object, (either after the url, or if you're not using a shortcut method like .get/.post the axios function just takes a config object. The good news is that config object is always passed along with the response so you can get to it in the interceptor and in the promise handers.
its available on the response objects as response.config and on the error as error.response.config
//#ts-ignore -- typescript will complain that your param isn't expected on the config object.
axios({
method: "get",
url: '/myapi/gocrazy',
// just piggyback any data you want to the end of config, just don't
// use any key's that axios is already expecting
PASSED_PARAM: true
}
//in the interceptor, config is attached to the object, and it keeps any extra keys you tacked on the end.
window.axios.interceptors.request.use(function (config) {
if (config.PASSED_PARAM == true) {
doSomethingAwesome();
}
return config;
}, function (error) {
return Promise.reject(error);
});
window.axios.interceptors.response.use(function (response) {
if (response.config.PASSED_PARAM == true) {
doSomethingAwesome();
}
return response;
}, function (error) {
if (error.response.config.PASSED_PARAM == true) {
doSomethingElseAwesome();
}
return Promise.reject(error);
});
Working solution
It's actually fairly simple to add parameters to the query with Axios interceptors when you send data.
axios.interceptors.request.use((config) => {
config.params = {my_variable: 'value'}
return config
})
axios allows to pass some additional request parameters:
axios.post('/api', `some body`,
{headers: {'Content-Type': ' text/html;charset=UTF-8'},
param: true});
and interceptor:
this.axios.interceptors.request.use(req => {
console.log(`${req.method}: ${req.param}`); //output: `/api: true`
return req;
});
I have tested it on version: 0.21.1
I ended up using the headers object. Not sure if that is recommended, or if it's anti-pattern. But anyhow it works. I am not entirely sure about how many bytes the header adds to the server request head, but I think it's neglectable.
In addition to the answer from DiamondDrake, the global typings can be overridden to not have to 'ts-ignore' the usage:
declare module 'axios' {
interface AxiosRequestConfig extends OriginalAxiosRequestConfig {
PASSED_PARAM: boolean;
}
}
You cannot pass param but you can update the passed param config. request interceptor logic runs before executing requests. It is kinda middlewares So maybe you need to access to tokens and update the request headers
axios.interceptors.request.use(
(config) => {
// or maybe you need to read the stored cookies
const user = localStorage.getItem("user");
if (user) {
// If user exists get the token
const token = JSON.parse(user).token;
// and then update the headers
config.headers.Authorization = `Bearer ${token}`;
}
// maybe u need to refresh the access tokens, you do it in interceptor
return config;
},
(err) => {
return Promise.reject(err);
}
);
The answer of https://stackoverflow.com/users/12706095/zack is kinda correct.
You should create an axios.d.ts file including the following lines, the rest is done by TypeScript.
import "axios";
declare module "axios" {
export interface AxiosRequestConfig {
/** A custom axios request config param that can be used anywhere now. */
myParam?: boolean;
}
}
Now TypeScript won't bother you anymore when you want to use this custom property anywhere when accessing the AxioRequestConfig, e.g. your interceptor.
See Axios typescript customize AxiosRequestConfig for more info.

How to return value in VueJS Mixin

I'm currently developing a webapp in Vuejs. I created a Mixin that I can access globally which handles any request to my api:
export default {
data() {
return {
apiURL: 'http://example.com/api',
timeout: 10000,
};
},
methods: {
callAPI(method, url, body) {
this.$http({
url: this.apiURL + url,
method,
body,
timeout: this.timeout,
})
.then((response) =>
response,
(response) => {
if (response.data.error) {
this.error = response.data.error;
} else {
this.error = 'We can\'t connect to the server. Please try again in a few minutes.';
}
return response;
});
// return 'test';
},
},
};
Now, in some of my components I call the api function:
const api_response = this.callAPI('POST', '/auth', credentials);
alert (api_response);
It works fine, but one thing doesn't work as expected. I expect my api_response constant to have the value of response but it is always undefined. So every time I got this alert with undefined. How is that possible? When I uncomment the return 'test' line it works: I got an alert with test, but it doesn't seem to work within the this.$http part...
Your callAPI has no return statement, so it returns undefined. If it returned your $http call, it still would not give you response, but would be a Promise, so you would want to do something like
let api_response;
this.callAPI('POST', '/auth', credentials).then((response) => api_response = response);

How to pass a javascript object into a child function scope

I have a function which is going to make a REST call, but it cannot do this until an auth token has been fetched.
So I wrapped the REST call in the 'then()' of the auth token call's promise, like so:
var RESTCall = function() {
return authTokenPromise.then(function() {
return $http.get('userService' {
userId: 1234,
someFlag: true
});
});
};
This has the result of waiting to fire off the call to the userService until the authToken promise (from the service that gets the auth token) has resolved.
The problem comes when I try to remove the hard-coded params that set userId and someFlag. What I want to do is this:
var RESTCall = function(params) {
return authTokenPromise.then(function() {
return $http.get('userService' {
userId: params.userId, // params is undefined
someFlag: params.flag // params is undefined
});
});
};
How do I pass params into the anonymous function scope created by then(function() {...})?

Categories