I have some queries from an API-Server that returns a json object that will be static over a user session, but not static forever.
It's a one-pager with Vue router.
How can I achieve that I:
can access this.myGlobals (or similar eg window.myGlobals) in all components, where my prefetched json-data from API-Server is stored.
My approach that is already working is to embed help.js via a mixin.
Oddly enough, I get hundreds of calls to this query. At first I thought that it only happened in the frontend and is chached, but the requests are actually sent hundreds of times to the server. I think it is a mistake of my thinking, or a systematic mistake.
i think the problem is, that the helper.js is not static living on the vue instance
main.js:
import helpers from './helpers'
Vue.mixin(helpers)
helpers.js:
export default {
data: function () {
return {
globals: {},
}
}, methods: {
//some global helper funktions
},
}, mounted() {
let url1 = window.datahost + "/myDataToStore"
this.$http.get(url1).then(response => {
console.log("call")
this.globals.myData = response.data
});
}
}
log in console:
call
SomeOtherStuff
(31) call
SomeOtherStuff
(2) call
....
log on server:
call
call
call (pew pew)
My next idea would be to learn vuex, but since its a easy problem, im not sure if i really need that bomb ?
You can use plugin to achieve this.
// my-plugin.js
export default {
install (Vue, options) {
// start fetching data right after install
let url1 = window.datahost + "/myDataToStore"
let myData
Vue.$http.get(url1).then(response => {
console.log("call")
myData = response.data
})
// inject via global mixin
Vue.mixin({
computed: {
myData () {
return myData
}
}
})
// or inject via instance property
Vue.prototype.$myData = myData
// or if you want to wait until myData is available
Vue.prototype.$myData = Vue.$http.get(url1)
.then(response => {
console.log("call")
myData = response.data
})
}
}
and use it:
Vue.use(VueResource)
Vue.use(myPlugin)
Related
I'm trying to implement Laravel's authorization & policy in Vue, by implementing a mixin which sends a GET request to a controller in the backend.
The problem is the v-if directive is receiving a Promise, which obviously does not resolve
Below is a very simplified version of what I'm trying to do:
The global mixin, auth.js
import axios from "axios"
export default {
methods: {
async $can (permission, $model_id) {
let isAuthorized = false;
await axios.get(`/authorization?${permission}&${model_id}`)
.then(function (response) {
isAuthorized = response.data.isAuthorized
})
.catch((error) => {
isAuthorized = false;
});
return isAuthorized;
}
}
}
The main entry file, app.js
import Auth from '#/auth';
Vue.mixin(Auth);
...
new Vue({...})
Component.vue
<template>
<div>
<div v-if="$can('do-this', 12)">
Show Me
</div>
</div>
</template>
<script>
export default {}
</script>
Is there any way to 'await' the async $can operation in v-if? Or am I approaching this from a totally wrong direction?
You don't need async/await there because axios returns a promise. I think you can call that function from the created hook. Instead of returning a value, change the related data attribute, and use it in v-if like so:
<div v-if="permissions['do-this__12']">
data() {
return {
permissions: {
'do-this__12': false,
'or-this__13': false,
},
}
}
methods: {
getPermissions() {
for (const key in this.permissions) {
this.can(key.split('__')[0], key.split('__')[1])
}
},
can(permission, model_id) {
axios.get(`/authorization?${permission}&${model_id}`)
.then(response => {
this.permissions[`${permission}__${model_id}`] = response.data.isAuthorized
})
.catch(error => {
this.permissions[`${permission}__${model_id}`] = false;
});
},
}
created() {
this.getPermissions();
}
I didn't try my code, let me know if it fails. BTW, extracting this implementation to a mixin will be a better idea. If you like to do that, just leave "permissions" object in the component and move everything else to the mixin.
But that approach isn't effective when you need multiple API calls for permissions. That's why I think you should pass the whole permissions object to the backend and make the work in the server:
iPreferThisBecauseOfSingleAPICall() {
axios.get(`/authorization`, this.permissions)
.then(({ data }) => this.permissions = data)
}
// AuthorizationController
public function index(Request, $request)
{
$permissions = [];
foreach($request->all() as $permission) {
// run your backend code here
}
return $permissions;
}
One final note, instead of asking for permission each time, loading all permissions at one can be the best idea.
I have a Vuex instance which loads data from API. The first time I access the store it should load the data from API, and when I access it again it should return the loaded data from the store.empresas. This is how my Vuex module looks like:
import Empresas from '#/api/empresas'
import moment from 'moment'
export default {
state: {
loaded: false,
lastLoadedDate: null,
empresas: []
},
getters: {
empresas: state => {
if (!state.loaded || moment().diff(state.lastLoadedDate, 'minutes') > 30) {
//Was not loaded yet or was loaded more than 30 minutes ago,
//Sould load from api -> actions.carregarEmpresas()
//Don't know how to proceed here
} else {
//Already loaded
return state.empresas
}
}
},
mutations: {
setEmpresas (state, payload) {
state.loaded = true
state.lastLoadedDate = moment()
state.empresas = payload
}
},
actions: {
carregarEmpresas ({ commit }) {
Empresas.listar()
.then(({ data }) => {
commit('setEmpresas', data.empresas)
})
}
}
}
The reason I need this is that I will need to access empresas in multiple files in my application, and I don't want to make an API call every time.
However, I don't know how to implement it inside the getter. Is it possible to do it?
This might be a duplicate of this post: Can I do dispatch from getters in Vuex. Check if the second answer helps you.
Basically, you shouldn't call actions from you getters. I would recommend you call the action at the initial load of the app. Then you will just use the getter without the conditional.
This is my Login.vue:
mounted() {
if (localStorage.login) this.$router.go(-1);
},
methods: {
axios.post(ApiUrl + "/login") {
...
}
then(response => {
...
localStorage.login = true;
this.$router.go(0); /* Reload local storage */
})
}
App.vue:
mounted() {
axios
.get("/user")
.then(response => {
localStorage.user_id = response.data.user.id;
localStorage.package_id = response.data.user.package_id;
})
},
Project.vue:
mounted() {
this.user_id = localStorage.user_id
this.package_id = localStorage.package_id
}
With that above code, I cannot get localStorage.user_id and localStorage.package_id as I expected. But if I change like the follow, it worked.
mounted() {
const self = this
setTimeout(function () {
self.user_id = localStorage.user_id
self.package_id = localStorage.package_id
self.getProject();
},1000)
}
But I think setTimeout not good in that case. Is there any way to refactor this code?
Thank you!
Try this: in your root component (it's usually const app = new Vue({ ... })) write the following:
import {localStorage} from 'localStorage'; // import your module if necessary
// this is relative to the way you manage your dependencies.
const app = new Vue({
//...
data: function() {
return {
localStorage: localStorage;
}
}
})
Now whenever you want to use localStorage, access it from root component like this:
this.$root.localStorage
Hope this solves your problem.
Don't know your project structure ,but I guess it's probably an async issue. You got the user information async, so when Project.vue mounted, the request is not complete yet. As a result, the localstorage is empty at the monent.
There are two solutions for this:
Make sure Project.vue is not rendered before userinfo is complete. For example, things like <project v-if="userinfo.user_id" /> should works.
Use some data binding libary like vuex to bind userinfo to Project.vue instead of assign it in lifecycle like mounted or created.
Hope it helps.
This is the method I'm using, pretty simple.
DailyCountTest: function (){
this.$store.dispatch("DailyCountAction")
let NewPatientTest = this.$store.getters.NewPatientCountGET
console.log(NewPatientTest)
}
The getter gets that data from a simple action that calls a django backend API.
I'm attempting to do some charting with the data so I need to assign them to variables. The only problem is I can't access the variables.
This is what the console looks like
And this is what it looks like expanded.
You can see the contents, but I also see empty brackets. Would anyone know how I could access those values? I've tried a bunch of map.(Object) examples and couldn't get any success with them.
Would anyone have any recommendation on how I can manipulate this array to get the contents?
Thanks!
Here is the Vuex path for the API data
Action:
DailyCountAction ({ commit }) {
axios({
method: "get",
url: "http://127.0.0.1:8000/MonthlyCountByDay/",
auth: {
username: "test",
password: "test"
}
}).then(response => {
commit('DailyCountMutation', response.data)
})
},
Mutation:
DailyCountMutation(state, DailyCount) {
const NewPatientMap = new Map(Object.entries(DailyCount));
NewPatientMap.forEach((value, key) => {
var NewPatientCycle = value['Current_Cycle_Date']
state.DailyCount.push(NewPatientCycle)
});
}
Getter:
NewPatientCountGET : state => {
return state.DailyCount
}
State:
DailyCount: []
This particular description of your problem caught my eye:
The getter gets that data from a simple action that calls a django backend API
That, to me, implies an asynchronous action and you might be getting a race condition. Would you be able to post a sample of your getter function to confirm my suspicion?
If that getter does indeed rely on an action to populate its contents, perhaps something to the effect of the following might do?
DailyCountTest: async () => {
await this.$store.dispatch('DailyCountAction')
await this.$store.dispatch('ActionThatPopulatesNewPatientCount')
let NewPatientTest = this.$store.getters.NewPatientCountGET
// ... do whatever with resulting array
}
You can also try with a computer property. You can import mapGetters
import { mapGetters } from 'vuex'
and later in computed properties:
computed: {
...mapGetters(['NewPatientCountGET'])
}
then you can use your NewPatientCountGET and it will update whenever the value changes in the store. (for example when the api returns a new value)
Hope that makes sense
This works, but I need to use mounted(){} to initiate the function which I think can be avoided but not sure how.
<script>
export default {
data () {
return {
domains: [],
}
},
methods: {
fetchDomains() {
let _this = this;
api._get({url: 'api/domains'})
.then(function (response) {
_this.domains = response.data;
})
}
},
mounted() {
this.fetchDomains()
}
}
</script>
This code doesn't work, but I like to do something like this. Initiating the function in data(){} itself.
<script>
export default {
data () {
return {
domains: this.fetchDomains(),
}
},
methods: {
fetchDomains() {
let data = [];
api._get({url: 'api/domains'})
.then(function (response) {
data = response.data;
})
return data
}
}
}
</script>
Thanks in advance.
Your first code snippet is the correct way to do it.
You can't initialize domains with the data from the API response because it is an async operation which may or may not be resolved successfully at some point in the future, well after the component is mounted. You might also want to do other things like keeping track of the async operation with a loading property which you set to true for the duration of the request.
Your component will initially be in a loading state which does not yet have any domains data, and you need to account for this. Show a loading spinner or something during this time.
I agree with Decade Moon that your first approach is the better way to do it (though you could use created instead of mounted).
The reason your second approach doesn't work is that you return an array and then replace the local variable's value with a different array. What you need to do is populate the array you returned.
new Vue({
el: '#app',
data() {
return {item: this.getItem()}
},
methods: {
getItem() {
let val = [];
setTimeout(() => {
const result = ['first','second','third'];
val.push(...result);
}, 800);
return val;
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">{{item}}</div>
I might be deviating slightly from the question (since it explicitly mentions the data property), but I think this might be helpful. Personally, if I want to provide some data with more complex logic I use the computed property. This is great in my opinion and you can read more about it in the docs. The problem in this case is that it doesn't work entirely as expected with asynchronous operations...
However, there is a lovely little module called vue-async-computed which can be found here. It solves this specific problem by providing an asyncComputed property and keeps the code really clean!