How can I synchronize two $resource calls in AngularJS - angularjs

I am trying to do something like the following:
In my controller I have functions that use $recource call to get data from database. The service 'myService'
var fillSubData = function (containerToFill) {
resService.getSubDataFromDB(//$resource service
{params},
function (res) {
//do something with containerToFill with the result res add new values
}
);
}
var fillData = function (containerToFill) {
resService.getDataFromDB(//$resource service
{params},
function (res) {
//do something with containerToFill with the result res
fillSubData(containerToFill);
}
);
}
Controller
$scope.dataToFill;// object
var initialize = function () {
//by reference
myService.fillData(dataToFill);
// I need the dataToFill filled to do other thing with data recovered and built
angular.forEach(dataToFill.someArrayBuilt, function (item) {
//do something with item...
})
}
I need the dataToFill filled to do other thing with data recovered and built, but the resource calls are asyn, how can I do this?

Note that the resource actions return an object that contains a $promise property. You can use this to proceed with a callback once the async call has returned:
myService.fillData(dataToFill).$promise.then(function() {
// I need the dataToFill filled to do other thing with data recovered and built
angular.forEach(dataToFill.someArrayBuilt, function (item) {
//do something with item...
})
});
To enable this, I suggest that you simply have your fillData method return the result of the resource call:
var fillData = function (containerToFill) {
return resService.getDataFromDB(//$resource service ...

Related

React: Method finishing before data loaded

I am trying to retrieve some data from Yahoo Finance using an XHTML Request, which works. However, I am trying to display the data retrieved on my app, but the method to retrieve the data is returning "undefined" before the data has been loaded.
async componentDidMount() {
var tempData = await this.fetchAsync();
console.log(tempData)
this.handleLoad(tempData)
}
handleLoad = (num) => {
this.setState(state => ({
price: num
}));
}
async fetchAsync () {
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
const {params} = this.props.navigation.state;
var ticker = params.ticker;
var result;
var tempArray = [1];
var url = "https://yahoo-finance-low-latency.p.rapidapi.com/v8/finance/spark?symbols=" + ticker + "&range=2y&interval=1d"
xhr.addEventListener("readystatechange", function () {
if (this.readyState === this.DONE) {
result = JSON.parse(this.responseText);
tempArray = result[ticker]['close'];
testPrice = tempArray[tempArray.length-1]
console.log(testPrice)
var self = this;
return tempArray[tempArray.length-1]
}
});
xhr.open('get', url, true);
xhr.setRequestHeader("x-rapidapi-key", "my key");
xhr.setRequestHeader("x-rapidapi-host", "yahoo-finance-low-latency.p.rapidapi.com");
xhr.send();
}
I am using the componentDidMount() function to begin calling the methods to load the data, but when the app renders, the values are not displayed.
As you can see inside the fetchAsync() method, I return the value I need, but when I try and console.log the return from this method, I get undefined.
I have also tried moving this return to the end of the method, but when I use console.log here to ensure that tempArray has the data I need, it is empty.
I need to display tempArray[tempArray.length-1] on my screen, but the data is not loaded in time, and does not update even after it has loaded.
Your return tempArray[tempArray.length-1] inside the fetchAsync isn't actually returning from fetchAsync -- it's just returning from the callback function inside addEventListener. In fact, you don't actually have any code that is taking advantage of the async tag you have on that function.
One solution to this would be to call handleLoad directly from inside fetchAsync instead of return tempArray. (Of course, you'll want to make sure that you've bound this correctly to handleLoad).
Another solution would be to pass a callback function into fetchAsync that you could call instead of returning. Then, at your call site, it might look something like this:
this.fetchAsync((tempData) => {
console.log(tempData)
this.handleLoad(tempData)
});
Finally, a third solution would be to switch from XMLHTTPRequest to fetch, and then you could take advantage of async/await and actually make that fetchAsync method async (and be able to return a value from it).

Why is my service running before anything else?

I'm new to services, factories, etc; so there's still a lot I don't understand.
I have a ui-grid. When a row is selected, I want to use that data as a param in a service to get REST data to populate another grid.
This is what I think it should be doing:
gridOne is registered => Row Selected => Send selectedRow.id to Service => Service GETs data => data populates grid 2
This is what its actually doing:
Service GETs data => Error because selectedRow.id is not defined.
01| $scope.gridOne.onRegisterApi = function(gridApi){
02| $scope.gridOneApi = gridApi
03| $scope.gridOneApi.selection.on.rowSelectionChanged(null, function(row){
04| $scope.gridOneSelectedRow = $scope.gridOneApi.selection.getSelectedRows()[0]
05|
06| // v---Breakpoint on this line triggered before any grid is built---v
07| myService.getAllObjects($scope.gridOneSelectedRow.id).then(response => {
08| $scope.grid2.data = response
09| }
10| })
11| }
My service looks like this:
app.service('myService', function ($http) {
return {
get: getObjects
}
function getOjects(id) {
let url = `http://${domain}/object/${id}`
return $http.get(url).then(response => {
return response
}).catch(error => {
return error
})
}
}
Why is the service function running before everything else?
if you are writing a service, you should not return a object/function/something, your implementation function is used as a constructor to newed up and create instance of your service.
so if you want to use a service, a sample for your myService will be
app.service('myService', function ($http) {
function getOjects(id) {
//you should properly replace your domain, may be with the same domain by default if you start from object/...
let url = `http://${domain}/object/+ id`
return $http.get(url).then(response => {
return response.data
}).catch(error => {
return error
})
}
this.getAllObjects = getOjects;
})
in case of factory
app.factory('myService', function ($http) {
function getOjects(id) {
//you should properly replace your domain, may be with the same domain by default if you start from object/...
let url = `http://${domain}/object/+ id`
return $http.get(url).then(response => {
return response.data
}).catch(error => {
return error
})
}
return {getAllObjects: getOjects};
})
and in the injecting end you don't need to change the code the way you are using it to load data, just wrote the code in sync of use
and also, I wnder, why you are trying to select before the data is loaded, and inside the handler of row selection you want to call the data when now row is present at all, if the data is not loaded, right? I hope you are loading data for another grid grid2 on selection of a row of grid1, and then want to load corresponding data related to that row to grid2.
Feel free to comment, whatever doubt you still have.
Good Luck :)

mapping the response to corresponding request

I am making $http request to multiple environment and processing after I get all the responses. I am using the code below:
$q.all(Object.keys($rootScope.envs).map(request)).then(function(res){
var results = {};
for (var env in res) {
results[env] = res[env].data;
}
}, function(err){
console.error(err);
});
function request(env) {
return $http.get(callService.getDomainUrl()+'/'+$rootScope.envs[env]+ '/hosts.json');
}
The above code works fine, but the results object looks like below:
{
0: {data:{}},
1: {data:{}},
2: {data:{}},
3: {data:{}}
}
I want the corresponding response for each key and the results should be like
{
env1: {data:{//data for env1}},
env2: {data:{//data for env2}},
env3: {data:{//data for env3}},
env4: {data:{//data for env4}},
}
How to map the corresponding response to the key? Please let me know how to get this as this is asynchronous request. Should I have something from the API to know which env the API is coming from?
I think the simplest way would be to push the result handling into the request function, that way you still have the 'env' value in scope.
var results = {};
$q.all(Object.keys($rootScope.envs).map(request)).then(function(res){
// Do something with 'results' here.
}, function(err){
console.error(err);
});
function request(env) {
return $http.get(callService.getDomainUrl()+'/'+$rootScope.envs[env]+ '/hosts.json')
.then(function(res) { results[env] = res.data; return env; });
}
Another option would be to replace my return env with return [env, res.data] and then you can go back to creating the results object as in your original code.
The important thing here is to remember you can handle the $http.get promises individually as well as using the promises from the call to then in $q.all.

AngularJS Restangular and get a single url

I have these "methods" in my angular service that use restangular to get remote data where the respose is this:
{
"1105":{"title":"","field_nazione":{"und":[{"value":null,"format":null,"safe_value":""}]},"field_redazionale":{"und":[{"value":null}]}},
"1110":{"title":"","field_nazione":{"und":[{"value":null,"format":null,"safe_value":""}]},"field_redazionale":{"und":[{"value":null}]}}
};
function restfulService(ipCookie,Restangular) {
return {
//Setta la directory di partenza del service rest. In questo modo non devo sempre definirlo
restfulBase : function() {
return Restangular.oneUrl('rest','http://MYREMOTEHOST/rest');
},
getAllCity : function () {
return this.restfulBase().get('cities', {'all':1}, {}, {'X-CSRF-Token': tokenVar});
},
....
};
Why when I call getAllCity() the url is :
http://MYDOMAIN/rest?0=c&1=i&2=t&3=i&4=e&5=s
?
If I use this :
Restangular.oneUrl('rest','http://MYDOMAIN/rest/cities').get({all : 1});
I have no problems.
I have tried changing my app to set Restangular.setBaseUrl() in .config() method and then changing my service to use Restangular.all('cities').get() but I have an error about "strings".
If I use getAll() I have an error about "getLists() want array and not objects".
So: which is the correct way to use Restangular ? I have read online documentation and tutorial, but I have not understand how to retrieve elements in the right way. And to "post".
Thanks and sorry for this stupid question.
I believe the problem is that you set your base as "one" type of resource. The "one" method is not for setting the base urls. The way I set the base url is like this:
angular.module('app')
.config(['RestangularProvider', function (RestangularProvider) {
// Set the base URL
RestangularProvider.setBaseUrl('http://MYREMOTEHOST/rest');
}]);
And then I access each resource, depending on its type with "one" or "all", like so:
function restfulService(ipCookie,Restangular) {
return {
getAllCity : function () {
return Restangular.all('cities').getList();
},
....
};
You can also set the default params in the config phase, or per request at runtime.
The "getLists() want array and not objects" error you get when you use "all" is because it accepts only arrays as a response, therefore you need to intercept the data before and parse it in an array. In your case, you are getting an object, so you can parse it like this:
// this happens at runtime, either in your implementation code or in a more global config file, like:
Restangular.addResponseInterceptor(function (data, operation, what, url, response, deferred) {
// If the what is set, than just grab it and return it
if (data.hasOwnProperty(what)) {
return data[what];
} else {
// Otherwise make it an array
var arr = [];
for (var key in data) {
arr.push(data[key]);
}
return arr;
});
Hope this helps.

How to roll back changes when there is an error in a promise chain

In my angular app I want to make changes to several locations in my firebase with a mix of transactions and set. I have written a promise chain with a little help. Now I need to handle any errors that may occur.
In the event of an error on any of the promises I would want to roll back any changes made in firebase (the successful promises) and alert the user to the failure.
Current code below
$scope.addNewPost = function() {
var refPosts = new Firebase(FBURL).child('/posts').push();
// Get tags into array for incrementing counters
var tags = $scope.post.tags.split(', ');
var allPromises = [];
// Iterate through tags and set promises for transactions to increment tag count
angular.forEach(tags, function(value, index){
var dfd = $q.defer();
var refTag = new Firebase(FBURL).child('/tags/' + value);
refTag.transaction( function (current_value) {
return current_value + 1;
}, function(error, committed, snapshot) {
if (committed) {
dfd.resolve( snapshot );
} else {
dfd.reject( error );
}
});
allPromises.push( dfd.promise );
});
// Add promise for setting the post data
var dfd = $q.defer();
refPosts.set( $scope.post, function (error) {
if (error) {
dfd.reject(error);
} else {
dfd.resolve('post recorded');
}
});
allPromises.push( dfd.promise );
$q.all( allPromises ).then(
function () {
$scope.reset(); // or redirect to post
},
function (error) {
// error handling goes here how would I
// roll back any data written to firebase
alert('Error: something went wrong your post has not been created.');
}
);
};
So what I need to know is how do I roll back any changes that happen to my firebase data in the event that one of these promises fail. There could be any number of updates happening in firebase. (for example: 3 tags being incremented via transaction and the post data being set)
How would I write the failure function to calculate what was successful and undo it? If this is this even possible.
--------------- sub question from original post has been solved ---------------
Also how do you force errors? I've tried setting a variable like below but it doesn't seem to work, is there something wrong with my .then?
refPosts.set( $scope.post, function (error) {
var forceError = true;
if (forceError) {
dfd.reject(forceError);
} else {
dfd.resolve('post recorded');
}
allPromises.push( dfd.promise );
});
There are two instances of this line, and they are both in the wrong place:
allPromises.push( dfd.promise );
In the first block, it should be in the last statement in the forEach callback, not in the transaction callback.
In the second block, it should be after the call to set(), not in the callback.
The way your code is written now, $q.all() is getting an empty array of promises. That could also be what's interfering with the forceError test you're attempting.

Resources