I created a new vue project using the command vue create axe using vue-cli-3.0.016beta. Then installed axios using npm install axios --save. In the main.js file I imported axios as shown below.
import Vue from 'vue'
import App from './App.vue'
import axios from 'axios'
Vue.config.productionTip = false
Vue.use(axios)
new Vue({
render: h => h(App)
}).$mount('#app')
There is not a bit of code change other than this. Still I get an error like the following:
Unhandled promise rejection
TypeError
columnNumber: 7
fileName: "http://localhost:8080/app.js line 1065 > eval"
lineNumber: 57
message: "parsed is undefined"
stack: "isURLSameOrigin#webpack-internal:///./node_modules/axios/lib/helpers/isURLSameOrigin.js:57:7\ndispatchXhrRequest#webpack-internal:///./node_modules/axios/lib/adapters/xhr.js:109:50\nPromise#webpack-internal:///./node_modules/core-js/modules/es6.promise.js:177:7\nxhrAdapter#webpack-internal:///./node_modules/axios/lib/adapters/xhr.js:12:10\ndispatchRequest#webpack-internal:///./node_modules/axios/lib/core/dispatchRequest.js:59:10\nrun#webpack-internal:///./node_modules/core-js/modules/es6.promise.js:75:22\nnotify/<#webpack-internal:///./node_modules/core-js/modules/es6.promise.js:92:30\nflush#webpack-internal:///./node_modules/core-js/modules/_microtask.js:18:9\n"
__proto__: Object { stack: "", … }
I want to axios globally to use interceptors, hence calling it here in main.js. But if I use it in a view-page there is no error!
is this a bug or I'm doing it wrong? Kindly help me to fix this and use axios globally.
Thanks
so the error I see is here
Vue.use(axios)
Vue.use expects a vue installable plugin.
You could have a look at vue-axios
import axios from 'axios'
import VueAxios from 'vue-axios'
Vue.use(VueAxios, axios)
but I would highly discourage it.
It's best to create your own ApiHandler.js file that handles all the remote stuff separately, and you can easily call from anywhere including vue components and vuex.
here is the beginning of my class
<script>
import axios from 'axios';
class ApiHandler{
constructor(apiUrl) {
this.axios = axios;
this.apiUrl = apiUrl || ''; // this line allow passing a custom endpoint for testing
this.config = {
headers: { 'Cache-Control': 'no-cache' }, // can setup to prevent all caching
baseURL: this.apiUrl,
};
}
/**
* #param {Object} payload
* #param {String} payload.username
* #param {String} payload.password
*/
login({ username, password }) {
return new Promise((resolve, reject) => {
this.axios.post('/api/login', { username: username.toLowerCase(), password }, this.config)
.then((response) => {
if (response.code === 200 && response.body && response.body.token) {
resolve(response.body.token);
} else {
reject('Bad Login');
}
})
.catch((err) => {
reject('internal error');
});
});
}
}
</script>
you can then call this from anywhere by...
<script>
import ApiHandler from '../lib/ApiHandler';
const apiRequest = new ApiRequest();
// and then anywhere in the script
let payload = {
username:'someuser',
password:'somepassword',
};
apiRequest.login(payload)
.then(()=>{
// yay - I'm logged in
})
.catch(err => {
// oh oh, display error
})
</script>
this gives you much more flexibility and allows you to separate the remote actions and allows doing first-leg response handling separate of your component, which allows more re-usability.
instead of
Vue.use(axios);
you should
Vue.prototype.$axios = axios;
then you can use it globally
login() {
this.$axios.post('<host>/api/login', data)
.then((res) => { // dosomething })
.catch((err) => { // dosomething });
}
if you want to add globally interceptors with axios, you can
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});
// and
Vue.prototype.$axios = axios;
Related
I have several components that I need to check if the user logged on/has valid access token
I currently do check this inside a Vue component method using the contents of isLoggedOut function below. I am thinking that I might need to create an external js file and import this js everywhere that I need to check of credentials. So js function will look sthg like below. However this function also resets the cookies in the component. see this.$cookies. I don't think this is possible due to scoping.
So how can I import functions (like from a utility js file) that also changes this objects? Or is there a better way of what avoiding code duplication in Vue/check for log out in multiple components using same code
import axios from "axios";
function isLoggedOut() {
axios.defaults.withCredentials = true;
const isLoggedOut = True;
const path = `/user_authentication/protected`;
axios
.get(path, { withCredentials: true })
.then((response) => {
message = response.data["user"];
isLoggedOut = false;
return isLoggedOut;
})
.catch((error) => {
console.error(error);
this.$cookies.remove("csrf_access_token");
isLoggedOut = true;
return isLoggedOut;
});
}
Create an index.ts file in a folder named utils and export the funtion isLoggedOut.
Pass the Vue app to the function isLoggedOut as a prop and call the vue methods.
import Vue from 'vue'
import axios from "axios";
export function isLoggedOut(app: Vue) {
axios.defaults.withCredentials = true;
const isLoggedOut = True;
const path = `/user_authentication/protected`;
axios
.get(path, { withCredentials: true })
.then((response) => {
message = response.data["user"];
isLoggedOut = false;
return isLoggedOut;
})
.catch((error) => {
console.error(error);
app.$cookies.remove("csrf_access_token");
isLoggedOut = true;
return isLoggedOut;
});
}
Component
import { isLoggedOut } from '~/utils'
export default {
methods: {
logOut() {
// Passing the Vue app
isLoggedOut(this)
}
}
}
I have tried all solutions here and am getting the same error. I must be doing something wrong.
I want to use Axios globally and not have to import it in every component I need to use it. So I created its Instance, attached it to the Vue app and am still getting
Error in mounted hook: "TypeError: Cannot read property 'get' of undefined"
found in
---> <Home> at src/views/app/Home.vue
<AppLayout> at src/layouts/AppLayout.vue
<Index> at src/views/app/index.vue
<App> at src/App.vue
<Root>
Here is the instance that i'm creating called axios-instance.js
import axios from 'axios'
import Vue from "vue";
import {apiUrl} from "./constants/config";
const devInstance = createInstance(apiUrl);
// const productionInstance = createInstance("http://localhost:3000"); // will change later
function createInstance(baseURL){
return axios.create({
baseURL,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer 9|b4DWuYCXyEK1H07KB1iiGXyRk2auEl5m1okDVe4B' //`Bearer ${localStorage.token}`
}
});
}
export default {
install () {
Vue.prototype.$http = devInstance
}
};
Then inside the main.js file, I am importing the instance and attaching it to Vue using Vue.use, see below.
import Vue from 'vue';
import http from './axios-instance';
//...
//setup axios
Vue.use(http)
//...
After that, I am trying to use it in an action but it's failing with the error message I showed you up there. Here is how am using it in action
//...
getDashboard({commit}) {
//get home info
this.$http.get('sacco/home')
.then(({status, data}) => {
console.log(status, data)
// commit('updateDashBoard', payload);
})
.catch((error) => {
console.log(error)
});
}
//...
I am trying to use an instance of an object in a boot file where the instance is created in another boot file. The documentation [0] talks about using an object instance from a boot file and it works fine when using the instance in a component. I would like to access the instance in another boot file.
First boot file that creates the instance looks like this:
import { AuthService } from '../authorization/AuthService';
let oidc = null
export default ({ router, store, Vue }) => {
const OIDC = new AuthService();
router.beforeEach((to, from, next) => {
const allowAnonymous = to.matched.some(record => record.meta.allowAnonymous)
if (allowAnonymous) {
next()
} else {
var isAuthenticated = OIDC.isAuthenticated()
if (isAuthenticated) {
next()
} else {
OIDC.signIn()
}
}
})
Vue.prototype.$oidc = OIDC
oidc = OIDC
}
export { oidc }
And I am trying to use the oidc instance in another boot file like this:
import oidc from "boot/oidc-service"
import axios from 'axios'
let axiosInstance = null;
export default ({ app, router, store, Vue }) => {
const AxiosInstance = axios.create({
baseURL: window.env.BASE_URL
})
AxiosInstance.interceptors.request.use(function (config) {
return oidc.getAccessToken().then(token => {
config.headers.Authorization = `Bearer ${token}`
return config
})
}, (error) => {
return Promise.reject(error)
})
Vue.prototype.$axios = AxiosInstance
axiosInstance = AxiosInstance
}
export { axiosInstance }
I import them in the following order:
boot: [
'oidc-service',
'axios',
...
If I export the class instead of the instance, I can instantiate it and code works as expected. I would like for the oidc object to be a singleton however.
How can I use the instance of oidc in my axios setup?
[0] https://quasar.dev/quasar-cli/boot-files#Accessing-data-from-boot-files
Unless I'm missing something... if oidc is not null, return it, otherwise continue with the initialization:
import { AuthService } from '../authorization/AuthService';
let oidc = null
export default ({ router, store, Vue }) => {
if(oidc !== null) return oidc;
// else continue...
how would i go about attaching axios / axios interceptor globally to nuxt (so its available everywhere), same how i18n is attached ?
The idea is that i would like to have a global axios interceptor that every single request goes through that interceptor.
Thanks
you can create a plugin called axios (/plugins/axios.js)
import Vue from 'vue';
import axios from 'axios';
axios.interceptors.request.use((config) => {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
Vue.use(axios);
then define this in nuxt.config.js
module.exports = {
//....
plugins: [
'~/plugins/axios',
],
//....
};
thats all, your interceptor is now working globally
It's hidden in the documentation - https://nuxtjs.org/docs/2.x/directory-structure/plugins
See number 3 of the first photo:
// plugins/axios.js
export default function ({ $axios, redirect }) {
$axios.onError(error => {
if (error.response.status == 404) {
redirect('/sorry')
}
})
}
then define this in nuxt.config.js
module.exports = {
//....
plugins: [
'~/plugins/axios',
],
//....
};
Maybe will be helpful for someone.
It just sets the lang parameter for every request.
Сreate a plugin called axios (/plugins/axios.js). Put it there:
export default function ({ $axios, app, redirect }) {
$axios.onRequest(config => {
config.params = config.params || {}; // get existing parameters
config.params['lang'] = app.i18n.locale;
})
$axios.onError(error => {
const code = parseInt(error.response && error.response.status)
if (code === 400) {
redirect('/400')
}
})
}
Add in nuxt.config.js:
module.exports = {
plugins: [
'~/plugins/axios'
]
};
Create a new module, call it request.js for example.
import axios from 'axios'
const instance = axios.create({
baseURL: 'http://example.org' // if you have one
})
// Put all interceptors on this instance
instance.interceptors.response.use(r => r)
export default instance
Then simply import that instance whenever you need it and use it like it was a normal axios instance:
import request from './request'
await request.get('/endpoint')
// or use promises
request.get('/endpoint').then(data => data)
If you really need it globally you can use the following code in your entry point of the application:
import request from './request'
global.request = request
// use it:
await request.get('example.org')
Or you can add it to the vue protype
Vue.prototype.$request = request
// in your component:
this.$request.get()
I'd advice against it though.
I store token after success login call in vuex store like this:
axios.post('/api/auth/doLogin.php', params, axiosConfig)
.then(res => {
console.log(res.data); // token
this.$store.commit('login', res.data);
})
axiosConfig is file where I only set baseURL export default { baseURL: 'http://localhost/obiezaca/v2' } and params is just data sent to backend.
My vuex file looks is:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
logged: false,
token: ''
},
mutations: {
login: (state, response) => {
state.logged = true;
state.token = response;
console.log('state updated');
console.log('state.logged flag is: '+state.logged);
console.log('state.token: '+state.token);
},
logout: (state) => {
state.logged = false;
state.token = '';
}
}
});
It is working correctly, I can re-render some of content in my SPA basing on v-if="this.$store.state.logged" for logged user. I'm able to access this.$store.state.logged from any component in my entire app.
Now I want to add my token to every request which call my rest API backend. I've created basic axios http interceptor which looks like this:
import axios from 'axios';
axios.interceptors.request.use(function(config) {
const token = this.$store.state.token;
if(token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, function(err) {
return Promise.reject(err);
});
Now I have 2 problems/questions about it.
I know that it is available to use this.$store.state.logged or this.$store.state.token across every component but can I use it same way in single javascript file?
Where should I execute/start my interceptor javascript file? It is independent file which lays in my app main folder but I am not calling it anywhere, in angularJS which I was working before, I had to add $httpProvider.interceptors.push('authInterceptorService'); in config but I don't know how to do same thing in vue architecture. So where should I inject my interceptor?
EDIT
I followed GMaiolo tips I added
import interceptor from './helpers/httpInterceptor.js';
interceptor();
to my main.js file and I refactor my interceptor to this:
import axios from 'axios';
import store from '../store/store';
export default function execute() {
axios.interceptors.request.use(function(config) {
const token = this.$store.state.token;
if(token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, function(err) {
return Promise.reject(err);
});
}
Result of this changes is that every already existing backend calls ( GET ) which don't need token to work stopped working but it is logical because I didn't clarified to which request it should add token so it is trying to add it everywhere and in my interceptor something is still wrong and that is why every already exisitng request stopped working.
When I try to do backend POST call in browser console I still get this error:
TypeError: Cannot read property '$store' of undefined
Although I import store to my interceptor file. Any ideas? I can provide some more information if any needed.
I additionally add screenshot of this main, store and interceptor tree structure so you can see that I'm importing fron correct path:
1.
First of all I'd use a Vuex Module as this Login/Session behavior seems to be ideal for a Session module. After that (which is totally optional) you can set up a Getter to avoid accessing the state itself from outside Vuex, you'd would end up with something like this:
state: {
// bear in mind i'm not using a module here for the sake of simplicity
session: {
logged: false,
token: ''
}
},
getters: {
// could use only this getter and use it for both token and logged
session: state => state.session,
// or could have both getters separated
logged: state => state.session.logged,
token: state => state.session.token
},
mutations: {
...
}
With those getters set, you can get the values a bit easier from components. With either using this.$store.getters.logged (or the one you'd want to use) or using the mapGetters helper from Vuex [for more info about this you can check the getters docs]:
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
...mapGetters([
'logged',
'token'
])
}
}
2.
I like to run Axios' interceptors along with Vue instantation in main.js creating, importing and executing an interceptors.js helper. I'd leave an example so you get an idea, but, then again, this is my own preference:
main.js
import Vue from 'vue';
import store from 'Src/store';
import router from 'Src/router';
import App from 'Src/App';
// importing the helper
import interceptorsSetup from 'Src/helpers/interceptors'
// and running it somewhere here
interceptorsSetup()
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App }
});
interceptors.js
import axios from 'axios';
import store from 'your/store/path/store'
export default function setup() {
axios.interceptors.request.use(function(config) {
const token = store.getters.token;
if(token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, function(err) {
return Promise.reject(err);
});
}
And there you'd end up having all the behavior cleanly encapsulated.
I did the same logic. however, I just change the file name. I used axios/index.js but the store is undefined there. so I just change the file name axios/interceptor.js and Don't know store data is accessible look at my below image