I am trying to update taxParentId with the new id that i retrieve with my API call inside the getTaxParentId function, but I cannot get it to change. I can console.log the value fine inside the method, but it won't update it. It seems to be an issue of scope, but i have set $this = this to take care of this, however, it is not working.
the getPostType method works fine and properly updates the data value.
var newVue = new Vue({
el: '#app',
data() {
return{
posts: [],
taxonomy: '',
postType: '',
taxParentSlug: '',
taxParentId: 0
}
},
created (){
let $this = this;
this.getPostType(location.href);
this.getTaxParent(location.href)
this.getTaxParentId();
this.getPosts();
},
methods: {
getPostType: function(currentURL){
if (currentURL.includes('residential')) {
this.postType = 'residential';
}else if(currentURL.includes('commercial')){
this.postType = 'commercial';
}else if (currentURL.includes('auto')) {
this.postType = 'auto';
}
},
getTaxParent: function(currentURL){
if (currentURL.includes('solar')) {
this.taxParentSlug = 'solar';
}else if(currentURL.includes('decorative')){
this.taxParentSlug = 'decorative';
}else if (currentURL.includes('safety-security')) {
this.taxParentSlug = 'safety-security';
}
},
getTaxParentId: function(){
let $this = this;
axios
.get(apiRoot + $this.postType + '-categories')
.then(function (response) {
response.data.forEach(function(item){
if (item.slug == $this.taxParentSlug) {
$this.taxParentId = item.id;
}
});
}
)
},
getPosts: function(){
let $this = this;
console.log(apiRoot + $this.postType + '-categories?parent=' + $this.taxParentId)
axios
.get(apiRoot + $this.postType + '-categories?parent=' + $this.taxParentId)
.then(function (response) {
$this.posts = response.data;
console.log($this.posts)
}
)
},
},
});
Because of the async, add watchers to your data, and log there.
watch:{
posts(value){console.log(value))},
taxParentId(value){console.log(value))}
}
Ideally you would get a promise from each call, and then wait for them all. If one call is dependent on another, you need to put the second call in a then() block, or even better, await it (async/await)
Using this, all you need to do is return the promise, and it will be synchronized.
async created (){
let $this = this;
await this.getPostType(location.href);
await this.getTaxParent(location.href)
await this.getTaxParentId();
await this.getPosts();
},
So much cleaner then chaining then blocks. You can wrap the entire block in a SINGLE catch, and trap all exceptions AND all rejections. Of course, if the calls are not dependent, you may want to call them in parallel and not await.
Since you are already using promises, you should be able to build a promise chain to solve your async issue.
Take your current function:
```javascript
getTaxParentId: function(){
let $this = this;
axios
.get(apiRoot + $this.postType + '-categories')
.then(function (response) {
response.data.forEach(function(item){
if (item.slug == $this.taxParentSlug) {
$this.taxParentId = item.id;
}
});
}
)
},
and make it return a value, even if it is just the response
```javascript
getTaxParentId: function(){
let $this = this;
axios
.get(apiRoot + $this.postType + '-categories')
.then(function (response) {
response.data.forEach(function(item){
if (item.slug == $this.taxParentSlug) {
$this.taxParentId = item.id;
}
});
return response
}
)
},
Then in your created() function, you can chain the call..
created (){
let $this = this;
this.getPostType(location.href);
this.getTaxParent(location.href)
this.getTaxParentId()
.then(function (response) {
this.getPosts();
})
},
This should force this.getPosts() to wait for getTaxParentId to be complete.
Related
I'm new to VueJs and currently trying to load some data only once and make it globally available to all vue components. What would be the best way to achieve this?
I'm a little bit stuck because the global variables occasionally seem to become null and I can't figure out why.
In my main.js I make three global Vue instance variables:
let globalData = new Vue({
data: {
$serviceDiscoveryUrl: 'http://localhost:40000/api/v1',
$serviceCollection: null,
$clientConfiguration: null
}
});
Vue.mixin({
computed: {
$serviceDiscoveryUrl: {
get: function () { return globalData.$data.$serviceDiscoveryUrl },
set: function (newUrl) { globalData.$data.$serviceDiscoveryUrl = newUrl; }
},
$serviceCollection: {
get: function () { return globalData.$data.$serviceCollection },
set: function (newCollection) { globalData.$data.$serviceCollection = newCollection; }
},
$clientConfiguration: {
get: function () { return globalData.$data.$clientConfiguration },
set: function (newConfiguration) { globalData.$data.$clientConfiguration = newConfiguration; }
}
}
})
and in my App.vue component I load all the data:
<script>
export default {
name: 'app',
data: function () {
return {
isLoading: true,
isError: false
};
},
methods: {
loadAllData: function () {
this.$axios.get(this.$serviceDiscoveryUrl)
.then(
response => {
this.$serviceCollection = response.data;
let configurationService = this.$serviceCollection.services.find(obj => obj.key == "ProcessConfigurationService");
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
}
);
this.isLoading = false;
})
}
},
created: function m() {
this.loadAllData();
}
}
</script>
But when I try to access the $clientConfiguration it seems to be null from time to time and I can't figure out why. For example when I try to build the navigation sidebar:
beforeMount: function () {
let $ = JQuery;
let clients = [];
if (this.$clientConfiguration === null)
console.error("client config is <null>");
$.each(this.$clientConfiguration, function (key, clientValue) {
let processes = [];
$.each(clientValue.processConfigurations, function (k, processValue) {
processes.push(
{
name: processValue.name,
url: '/process/' + processValue.id,
icon: 'fal fa-project-diagram'
});
});
clients.push(
{
name: clientValue.name,
url: '/client/' + clientValue.id,
icon: 'fal fa-building',
children: processes
});
});
this.nav.find(obj => obj.name == 'Processes').children = clients;
The most likely cause is that the null is just the initial value. Loading the data is asynchronous so you'll need to wait for loading to finish before trying to create any components that rely on that data.
You have an isLoading flag, which I would guess is your attempt to wait for loading to complete before showing any components (maybe via a suitable v-if). However, it currently only waits for the first request and not the second. So this:
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
}
);
this.isLoading = false;
would need to be:
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
this.isLoading = false;
}
);
If it isn't that initial value that's the problem then you need to figure out what is setting it to null. That should be prety easy, just put a debugger statement in your setter:
$clientConfiguration: {
get: function () { return globalData.$data.$clientConfiguration },
set: function (newConfiguration) {
if (!newConfiguration) {
debugger;
}
globalData.$data.$clientConfiguration = newConfiguration;
}
}
Beyond the problem with the null, if you're using Vue 2.6+ I would suggest taking a look at Vue.observable, which is a simpler way of creating a reactive object than creating a new Vue instance.
Personally I would probably implement all of this by putting a reactive object on Vue.prototype rather than using a global mixin. That assumes that you even need the object to be reactive, if you don't then this is all somewhat more complicated than it needs to be.
var ChannelStatsView = Backbone.View.extend({
el: "#id-channel-stats",
initialize: function() {
var _this = this;
this.modelChannelList = new channelListModel();
this.modelChannelStats = new channelStatsModel();
this.channelstatsCollection = new channelStatsCollection();
this.channelNames = [];
this.listOfObjects = [];
this.modelChannelList.fetch({
success: function(model, response, options) {
model.set();
_this.formatChannelIds();
},
error: function(model, xhr, options) {
}
});
},
formatChannelIds: function() {
_this = this;
_.filter(_this.modelChannelList.toJSON(), function(channelObj) {
if (channelObj['isactive'] == true) {
_this.updateStats(channelObj['id'], channelObj['name']);
}
});
},
updateStats: function(id, name) {
var _this = this;
_this.modelChannelStats.fetch({
data: {
channel: id
},
processData: true,
success: function(model, response, options) {
_this.response = response;
_this.listOfObjects.push(_this.response.records[0]);
_this.channelNames.push(name);
}
}).done(function(model, response, options) {
_this.render();
});
},
render: function() {
var _this = this;
if (_this.listOfObjects.length == 0) {
} else {
_this.template = channelTemplate;
_this.$el.html(_this.template({
orderData: _this.listOfObjects,
channelNames: _this.channelNames
}));
}
}
});
In my code i am taking the response from one model.fetch query i.e this.modelChannelList and getting all the active id's then supplying it to another fetch to get the response i know this solution is really bad can someone help me how to make it faster and effective.
I am considering using Promises
The main issue you need to deal with here is the number of fetch requests that you are making. Promises are cool so I've included that too. Here's what I recommend you do:
1) Update your model class to assign the fetch function as a deferred
var channelListModel = Backbone.Model.extend({
initialize: function() {
// Assign the Deferred issued by fetch() as a property
this.deferred = this.fetch();
}
});
2) Modify your updateStats/formatChannels logic to create an array of ids and pass those through your fetch to get a complete data set. This will save tons of time by reducing the number of calls you have to make
initialize: function() {
// other stuff here...
this.modelChannelList.deferred.done(function(model) {
model.set();
view.formatChannelIds();
});
// other stuff here...
}
formatChannelIds: function() {
var _this = this,
ids = [];
_.filter(_this.modelChannelList.toJSON(), function(channelObj) {
if (channelObj['isactive'] == true) {
ids.push(channelObj['id']);
}
_this.updateStats(ids);
});
}
You will have to change up your data service a bit, but this is a change that is ultimately necessary anyways.
I think I'm writing my promise incorrectly and I couldn't figure out why it is caching data. What happens is that let's say I'm logged in as scott. When application starts, it will connect to an endpoint to grab listing of device names and device mapping. It works fine at this moment.
When I logout and I don't refresh the browser and I log in as a different user, the device names that scott retrieved on the same browser tab, it is seen by the newly logged in user. However, I can see from my Chrome's network tab that the endpoint got called and it received the correct listing of device names.
So I thought of adding destroyDeviceListing function in my factory hoping I'll be able to clear the values. This function gets called during logout. However, it didn't help. Below is my factory
app.factory('DeviceFactory', ['$q','User', 'DeviceAPI', function($q, User, DeviceAPI) {
var deferredLoad = $q.defer();
var isLoaded = deferredLoad.promise;
var _deviceCollection = { deviceIds : undefined };
isLoaded.then(function(data) {
_deviceCollection.deviceIds = data;
return _deviceCollection;
});
return {
destroyDeviceListing : function() {
_deviceCollection.deviceIds = undefined;
deferredLoad.resolve(_deviceCollection.deviceIds);
},
getDeviceIdListing : function() {
return isLoaded;
},
getDeviceIdMapping : function(deviceIdsEndpoint) {
var deferred = $q.defer();
var userData = User.getUserData();
// REST endpoint call using Restangular library
RestAPI.setBaseUrl(deviceIdsEndpoint);
RestAPI.setDefaultRequestParams( { userresourceid : userData.resourceId, tokenresourceid : userData.tokenResourceId, token: userData.bearerToken });
RestAPI.one('devices').customGET('', { 'token' : userData.bearerToken })
.then(function(res) {
_deviceCollection.deviceIds = _.chain(res)
.filter(function(data) {
return data.devPrefix != 'iphone'
})
.map(function(item) {
return {
devPrefix : item.devPrefix,
name : item.attributes[item.devPrefix + '.dyn.prop.name'].toUpperCase(),
}
})
.value();
deferredLoad.resolve(_deviceCollection.deviceIds);
var deviceIdMapping = _.chain(_deviceCollection.deviceIds)
.groupBy('deviceId')
.value();
deferred.resolve(deviceIdMapping);
});
return deferred.promise;
}
}
}])
and below is an extract from my controller, shortened and cleaned version
.controller('DeviceController', ['DeviceFactory'], function(DeviceFactory) {
var deviceIdMappingLoader = DeviceFactory.getDeviceIdMapping('http://10.5.1.7/v1');
deviceIdMappingLoader.then(function(res) {
$scope.deviceIdMapping = res;
var deviceIdListingLoader = DeviceFactory.getDeviceIdListing();
deviceIdListingLoader.then(function(data) {
$scope.deviceIDCollection = data;
})
})
})
Well, you've only got a single var deferredLoad per your whole application. As a promise does represent only one single asynchronous result, the deferred can also be resolved only once. You would need to create a new deferred for each request - although you shouldn't need to create a deferred at all, you can just use the promise that you already have.
If you don't want any caching, you should not have global deferredLoad, isLoaded and _deviceCollection variables in your module. Just do
app.factory('DeviceFactory', ['$q','User', 'DeviceAPI', function($q, User, DeviceAPI) {
function getDevices(deviceIdsEndpoint) {
var userData = User.getUserData();
// REST endpoint call using Restangular library
RestAPI.setBaseUrl(deviceIdsEndpoint);
RestAPI.setDefaultRequestParams( { userresourceid : userData.resourceId, tokenresourceid : userData.tokenResourceId, token: userData.bearerToken });
return RestAPI.one('devices').customGET('', { 'token' : userData.bearerToken })
.then(function(res) {
return _.chain(res)
.filter(function(data) {
return data.devPrefix != 'iphone'
})
.map(function(item) {
return {
devPrefix : item.devPrefix,
name : item.attributes[item.devPrefix + '.dyn.prop.name'].toUpperCase(),
};
})
.value();
});
}
return {
destroyDeviceListing : function() {
// no caching - nothing there to be destroyed
},
getDeviceIdListing : function(deviceIdsEndpoint) {
return getDevices(deviceIdsEndpoint)
.then(function(data) {
return { deviceIds: data };
});
},
getDeviceIdMapping : function(deviceIdsEndpoint) {
return this.getDeviceIdListing(deviceIdsEndpoint)
.then(function(deviceIds) {
return _.chain(deviceIds)
.groupBy('deviceId')
.value();
});
}
};
}])
Now, to add caching you'd just create a global promise variable and store the promise there once the request is created:
var deviceCollectionPromise = null;
…
return {
destroyDeviceListing : function() {
// if nothing is cached:
if (!deviceCollectionPromise) return;
// the collection that is stored (or still fetched!)
deviceCollectionPromise.then(function(collection) {
// …is invalidated. Notice that mutating the result of a promise
// is a bad idea in general, but might be necessary here:
collection.deviceIds = undefined;
});
// empty the cache:
deviceCollectionPromise = null;
},
getDeviceIdListing : function(deviceIdsEndpoint) {
if (!deviceCollectionPromise)
deviceCollectionPromise = getDevices(deviceIdsEndpoint)
.then(function(data) {
return { deviceIds: data };
});
return deviceCollectionPromise;
},
…
};
Even though I have managed to make my code work, there is something I don't understand. The following piece of code functions correctly:
socket.on('method', function() {
var payload = {
countrycode: '',
device: ''
};
var d1 = $q.defer();
var d2 = $q.defer();
$q.all([
geolocation.getLocation().then(function(position) {
geolocation.getCountryCode(position).then(function(countryCode){
payload.countrycode = countryCode;
d1.resolve(countryCode);
});
return d1.promise;
}),
useragent.getUserAgent().then(function(ua) {
useragent.getIcon(ua).then(function(device) {
payload.device = device;
d2.resolve(device);
});
return d2.promise
})
]).then(function(data){
console.log(data); //displays ['value1', 'value2']
})
});
Is there a better way of achieving this? Before I had only one deferred variable, i.e. varvar deferred = $q.defer(); but that way the .then() function returned an object with double the results.
So the few question I have are:
Do I need multiple $q.defer vars?
Is the above the best way to wait for two async calls to finish and populate the payload object?
socket.on('method', function() {
var payload = {
countrycode: '',
device: ''
};
geolocation.getLocation()
.then(function(position) {
return geolocation.getCountryCode(position);
})
.then(function(countryCode) {
payload.countrycode = countryCode;
return useragent.getUserAgent();
})
.then(function(ua) {
return useragent.getIcon(ua);
})
.then(function(device) {
payload.device = device;
console.log(data); //displays ['value1', 'value2']
});
});
read the promise chaining part
You could always separate your code into smaller semantic blocks like so:
getCountryCode = function() {
var d = $q.defer();
geolocation.getLocation()
.then(function(position) {
return geolocation.getCountryCode(position)
})
.then(function(countryCode) {
d.resolve(countryCode);
})
.fail(function(err) {
d.reject(err);
})
return d.promise;
};
getDevice = function() {
var d = $q.defer();
useragent.getUserAgent()
.then(function(ua) {
return useragent.getIcon(ua)
})
.then(function(device) {
d.resolve(device);
})
.fail(function(err) {
d.reject(err);
});
return d.promise;
}
That will shorten your actual parallel call ($q.all) quite a bit:
socket.on('method', function() {
$q.all([getCountryCode(), getDevice()])
.spread(function(countryCode, device) {
var payload = {
countryCode: countryCode,
device: device
};
// ... do something with that payload ...
});
});
To synchronize multiple asynchronous functions and avoid Javascript callback hell:
http://fdietz.github.io/recipes-with-angular-js/consuming-external-services/deferred-and-promise.html
I have an object
var actions = {
'photos': function()
{
var self = this; // self = actions
$.get('./data.php?get=photos', function(data)
{
self.result = data;
});
},
'videos': function()
{
var self = this;
$.get('./data.php?get=videos', function(data)
{
self.result = data;
});
}
};
Each function creates one more item in actions called result
Then, instead of switch I use this (works good):
if (actions[action])
{
actions[action](); // call a function
console.log(actions);
console.log(actions.result);
}
action is a variable with value photos or videos.
console.log(actions) gives this:
Object
message: function ()
messages: function ()
profile: function ()
profile-edit: function ()
result: "<div>...</div>"
__proto__: Object
So I think there is resultitem in actions with the value "<div>...</div>".
But, console.log(actions.result) returns undefined.
Why?
I know all this code may be rewrited, but I would like to understand the reason of undefined.
Because we are dealing with asynchronous requests, we use "callbacks".
A callback is called when an asynchronous request is ready. Your request will get a response, and you send that response with the callback. The callback handles the response.
var actions = {
'photos': function(callback)
{
$.get('./data.php?get=photos', callback);
},
'videos': function(callback)
{
$.get('./data.php?get=videos', callback);
}
};
var action = 'photos';
actions[action](function(data) {
console.log(data);
});
Since you ensist on keeping the values, I would use this structure:
var actions = {
'photos': function()
{
$.get('./data.php?get=photos', function() {
this.__callback('photos', data);
});
},
'videos': function()
{
$.get('./data.php?get=videos', function() {
this.__callback('videos', data);
});
},
'__callback': function(action, data) {
this.results[action].push(data);
},
'results': {
'photos': [],
'videos': []
}
};
var action = 'photos';
actions[action]();
// use a timeout because we are dealing with async requests
setTimeout(function() {
console.log(actions.results); // shows all results
console.log(actions.results.photos); // shows all photos results
console.log(actions.results.videos); // shows all videos results
}, 3000);
gaaah what a horrible piece of code...