how to get local storage token in vue methods property - vue.js

i've a vue app which requires a token when sending a request each time i try to send a request i keep getting token not defined... here's the error
this how i call my methods property in script tag
<script>
import { mapActions } from "vuex";
import axios from "axios";
export default {
name: "Products",
data() {
return {
addresses: [],
products: []
};
},
methods: {
onDeleteAddress(id, index) {
axios
.delete(`http://localhost:5000/api/addresses/${this.$route.params.id}`,
{
headers: {
Authorization: "Bearer" + token,
"x-access-token": token
}
}
)
.then(response => {
console.log(response);
})
.catch(error => {
console.log(error);
});
}
}
};
</script>
this my vue template
Delete
this works in my mounted life cycle hook but when i input it in my methods component i get an error
const token = localStorage.getItem("token");
please how can i get the token stored in my local storage and define it in my vue methods conponent

In first make sure the item is already stored in the localstorage
Second instead of calling it from the localstorage it is better to define it in main.js file as global variable so you can use it free every where
Example
Vue.prototype.$globalData = Vue.observable({ token: localStorage.getItem("token") });
And now you can use it in your methods like this
this.$globalData.token
You can as much as you want variable in the globalData object

Related

Nuxt access component's method or data in hook fetch/asyncData

On my component i have follow script:
<script>
export default {
data(){
return {
posts: [],
}
}
methods: {
async get_post(){
return await this.$axios('post')
}
}
}
</script>
I want to access my data and my methods from fetch or asyncData hook without using the axios directly there, tried "this" on fetch but only data is accessible but not the methods, on asyncData i can't even access the two.
according to the doc: Data Fetching
asyncData
you can only access the context in asyncData()(also, please remember the asyncData() is only avaiables in page components)
so you can code like
<script>
export default {
asyncData({ $axios }){
return $axios.post('/users')
}
}
</script>
fetch
but you can access the data and methods in fetch()
export default {
data: () => ({
posts: []
}),
async fetch() {
this.posts = await this.$http.$get('https://api.nuxtjs.dev/posts')
},
fetchOnServer: false,
// multiple components can return the same `fetchKey` and Nuxt will track them both separately
fetchKey: 'site-sidebar',
// alternatively, for more control, a function can be passed with access to the component instance
// It will be called in `created` and must not depend on fetched data
fetchKey(getCounter) {
// getCounter is a method that can be called to get the next number in a sequence
// as part of generating a unique fetchKey.
return this.someOtherData + getCounter('sidebar')
}
}

Passing OAuth token from one Vuejs component to another

I get OAuth token after successful OAuth login in a SuccessOAuth.vue component. I get the token details as follows:
checkforTokens(){
const queryString = this.$route.query;
console.log(queryString);
const token = this.$route.query.accessToken
console.log(token);
const secret = this.$route.query.tokenSecret
console.log(secret);
this.tokens.token = token;
this.tokens.secret = secret;
}
},
beforeMount() {
this.checkforTokens();
}
Now I want to use this token in another component apiCalls.vue where I use this token details to use call the API methods.
<script>
...
methods:{
getProductDetails() {
console.log("==========================================");
console.log(".. Get Product details....");
axios
.get("/auth/getShpDetails", {
params: {
token: this.tokens.token
}
})
.then(response => {
const productInfo = response.data;
console.log("Product info :" + productInfo);
});
},
}
</script>
How do I pass the token details from SuccessOAuth component to apiCalls. I tried using props method but I wasn't able to get the token value to the script tag, not sure about other methods used to pass i.e using $emit and using vuex. Please suggest the best way and the right solution for the problem.
As suggested by #Nishant Sham, I am just modifying the action method in index.js as seen below:
import Vue from 'vue'
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
token: ''
},
getters: {
getToken(state){
return state.token;
}
},
mutations: {
setToken(state, tokenValue){
state.token = tokenValue;
}
},
actions: {
setToken({commit}, tokenValue){
commit("setToken", tokenValue);
}
}
});
In your vue component you call getters and setters as follows:
<script>
//Set token value
var token = "dwe123313e12";//random token value assigned
this.$store.commit("setToken", token);
.....
//Get token value
var getToken = this.$store.getters.getToken;
</script>
You can keep your token inside Localstorage or cookies. And use as per your need. Here is the sample code for this:
const token = 'token'
export function getToken() {
return localStorage.getItem(token)
}
export function setToken(tokenData) {
return localStorage.setItem(token, tokenData)
}
export function removeToken() {
return localStorage.removeItem(token)
}
you can use Vuex for state management. Here is an article
One way to do this could be vuex
in the root, store create a token field and make one getter that you can call from any vue component and on any life cycle hook..
The second way can be that you set the token to localStorage and get/use it wherever you need it
I would prefer the vuex method that way it ensures a single source of truth...
Here is how to use vuex store
First of all install vuex depending on the vue version you are using, Generally, for the vue3 it is advisable to use npm i vuex#next
Create a Store folder inside your src folder and in there add the index.js with the following code
import { createStore } from "vuex";
import axios from "axios"; // I Use axios for making API CALLS hence this pkg
const store = createStore({
state() {
return {
token: null,
};
},
});
export default store;
This is the basic store and state of you app for now.
Lets start adding Actions first because actions are the async code used for making the API call and get the data from server
actions: {
async login(context, payload) {
try {
const result = await axios({
method: "POST",
url: "auth/login",
data: {
email: payload.email,
password: payload.password,
},
});
//If the Request Successed with Status 200
if (result.status === 200) {
//A: Extract the Token
const token = result.data.token;
//B. Token to LocalStorage Optional if you wish to set it to localstorgae
localStorage.setItem("token", token);
//c: UPDATE THE STATE by calling mutation
context.commit("setToken", {
token,
});
}
} catch (err) {
console.log(err);
}
},
},
Next step as you might have guessed adding mutation, which is used for updating your app state..
mutations: {
setToken(state, token) {
state.token = token;
},
},
Last the getter which you shall use to fetch the data either as computed inside your app components this is the
getters: {
getToken(state) {
return state.token;
},
},
Finally after all of this you index.js should look something like this
import { createStore } from "vuex";
import axios from "axios";
const store = createStore({
state() {
return {
token: null,
};
},
actions: {
async login(context, payload) {
try {
const result = await axios({
method: "POST",
url: "auth/login",
data: {
email: payload.email,
password: payload.password,
},
});
//If the Request Successed with Status 200
if (result.status === 200) {
//A: Extract the Token
const token = result.data.token;
//B. Token to LocalStorage Optional if you wish to set it to localstorgae
localStorage.setItem("token", token);
//c: UPDATE THE STATE by calling mutation
context.commit("setToken", {
token,
});
}
} catch (err) {
console.log(err);
}
},
},
mutations: {
setToken(state, token) {
state.token = token;
},
},
getters: {
getToken(state) {
return state.token;
},
},
});
export default store;
NOTE - This is a General representation of how the code for vuex should looks like there are a ton of other way to achive the same result, depending on you project requirment
The above code is not a final code, as it will need to be adjusted as per your test/example/project requirement

Nuxt.js - The best place for API calls

I'm new to Vue.js Nuxt and all front-end stuff.
I have a question about API calls. I'm not sure what is the right way, the best practice here.
I have a store. In that store, I have actions that are calling my API and sets state eg.
async fetchArticle({ state, commit }, uuid) {
const response = await this.$axios.get(`articles/${uuid}/`)
commit('SET_ARTICLE', response.data)
},
And that is fine it is working for one component.
But what if I want to just fetch the article and not changing the state.
To be DRY first thing that comes to my mind is to create the service layer that is fetching the data and is used where it is needed.
Is it the right approach? Where can I find some real-world examples that I can take inspiration from?
Using the repository pattern to abstract your API is definitely a good idea! Whether you use the #nuxtjs/axios module or the #nuxt/http module, you can pass either instance to your repository class/function. Below a real world example of an abstracted "repository.js" file.
export default $axios => resource => ({
index() {
return $axios.$get(`/${resource}`)
},
create(payload) {
return $axios.$post(`/${resource}`, payload)
},
show(id) {
return $axios.$get(`/${resource}/${id}`)
},
update(payload, id) {
return $axios.$put(`/${resource}/${id}`, payload)
},
delete(id) {
return $axios.$delete(`/${resource}/${id}`)
}
})
You can then create a plugin to initialize all different kinds of repositories for your endpoints:
import createRepository from '~/path/to/repository.js'
export default (ctx, inject) => {
const repositoryWithAxios = createRepository(ctx.$axios)
const repositories = {
posts: repositoryWithAxios('posts'),
users: repositoryWithAxios('users')
//...
}
inject('repositories', repositories)
}
Further read: Organize and decouple your API calls in Nuxt.js
I will an example of a service layer implementation for my portfolio to create my dashboard that shows some statics about my github and stackoverflow profiles, to do this i created a folder called services inside the project root :
pages
services
|_AxiosConfig.js
|_GitHubService.js
|_StackoverflowService.js
...
in the AxiosConfig.js file i put i created an axios instance with its configuration :
import axios from 'axios';
const clientAPI = url =>
axios.create({
baseURL: url,
withCredentials: false,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
});
export default clientAPI;
then in my GitHubService.js i imported that axios instance called clientAPI which i used to my requests :
import clientAPI from './AxiosConfig';
const baseURL = 'https://api.github.com';
export default {
getUser(name) {
return clientAPI(baseURL).get('/users/' + name);
},
getRepos(name){
return clientAPI(baseURL).get('/users/' + name+'/repos');
},
getEvents(name,page){
return clientAPI(baseURL).get('/users/' + name+'/events?per_page=100&page='+page);
},
getLastYearCommits(name,repo){
return clientAPI(baseURL).get('/repos/' + name+'/'+repo+'/stats/commit_activity');
}
};
then in my page i used asyncData hook to fetch my data :
import GitHubService from '../../services/GitHubService'
export default {
...
async asyncData({ error }) {
try {
const { data } = await GitHubService.getUser("boussadjra");
const resRepos = await GitHubService.getRepos("boussadjra");
return {
user: data,
repos: resRepos.data
};
} catch (e) {
error({
statusCode: 503,
message: "We cannot find the user"
});
}
}
I wanted to use axios in my service/service.js file, so instead of passing axios, I accessed it directly like this:
export default {
async fetchArticle() {
let response = await $nuxt.$axios.$get('/api-url')
return response
},
}
In Nuxt, if you want to just get the data without keeping it in your store, you could use the asyncData function, which asynchronously loads data (from API calls and the like) and pushes it into the component's data object before rendering.

Axios interceptor in vue 2 JS using vuex

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

How to structure api calls in Vue.js?

I'm currently working on a new Vue.js application. It depends heavily on api calls to my backend database.
For a lot of things I use Vuex stores because it manages shared data between my components. When looking at other Vue projects on github I see a special vuex directory with files that handles all the actions, states and so on. So when a component has to call the API, it includes the actions file from the vuex directory.
But, for messages for example, I don't want to use Vuex because those data is only important for one specific view. I want to use the component specific data here. But here is my problem: I still need to query my api. But I shouldn't include the Vuex actions file. So in that way I should create a new actions file. This way I have a specific file with api actions for vuex and for single components.
How should I structure this? Creating a new directory 'api' that handles actions for both vuex data and component-specific data? Or separate it?
I am using axios as HTTP client for making api calls, I have created a gateways folder in my src folder and I have put files for each backend, creating axios instances, like following
myApi.js
import axios from 'axios'
export default axios.create({
baseURL: 'http://localhost:3000/api/v1',
timeout: 5000,
headers: {
'X-Auth-Token': 'f2b6637ddf355a476918940289c0be016a4fe99e3b69c83d',
'Content-Type': 'application/json'
}
})
Now in your component, You can have a function which will fetch data from the api like following:
methods: {
getProducts () {
myApi.get('products?id=' + prodId).then(response => this.product = response.data)
}
}
Similarly you can use this to get data for your vuex store as well.
Edited
If you are maintaining product related data in a dedicate vuex module,
you can dispatch an action from the method in component, which will internally call the backend API and populate data in the store, code will look something like following:
Code in component:
methods: {
getProducts (prodId) {
this.$store.dispatch('FETCH_PRODUCTS', prodId)
}
}
Code in vuex store:
import myApi from '../../gateways/my-api'
const state = {
products: []
}
const actions = {
FETCH_PRODUCTS: (state, prodId) => {
myApi.get('products?id=' + prodId).then(response => state.commit('SET_PRODUCTS', response))
}
}
// mutations
const mutations = {
SET_PRODUCTS: (state, data) => {
state.products = Object.assign({}, response.data)
}
}
const getters = {
}
export default {
state,
mutations,
actions,
getters
}
Note: vue-resource is retired ! Use something else, such as Axios.
I'm using mostly Vue Resource.I create services directory, and there put all connections to endpoints, for e.g PostService.js
import Vue from 'vue'
export default {
get(id) {
return Vue.http.get(`/api/post/${id}`)
},
create() {
return Vue.http.post('/api/posts')
}
// etc
}
Then in my file I'm importing that service and create method that would call method from service file
SomeView.vue
import PostService from '../services/PostService'
export default {
data() {
item: []
},
created() {
this.fetchItem()
},
methods: {
fetchItem() {
return PostService.get(to.params.id)
.then(result => {
this.item = result.json()
})
}
}
}
Based on concept of Belmin Bedak`s answer, i have wrapped it all into a simple library:
https://github.com/robsontenorio/vue-api-query
You can request your API like this:
All results
// GET /posts?filter[status]=ACTIVE
let post = await Post
.where('status', 'ACTIVE')
.get()
Specific result
// GET /posts/1
let post = await Post.find(1)
Editing
// PUT /posts/1
post.title = 'Awsome!'
post.save()
Relationships
// GET /users/1
let user = await User.find(1)
// GET users/1/posts
let posts = await user
.posts()
.get()