Proper way of dealing with forEach Ajax calls in Angular - angularjs

I need to update the data for each object in an array using a for loop and once all the data is captured, run a function. I don't want to mix jQuery in this and do it the proper Angular way of doing
Here is what I am doing,
$scope.units = ['u1', 'u2', 'u3'];
$scope.data = null;
//get individual unit data
$scope.getUnitData = function(unit){
service.getUnitData(unit).success(function(response){
$scope.data.push({'id' : response.id , 'value' : response.value});
});
};
$scope.updateAllUnits = function(){
$scope.data = null ; //remove existing data
angular.forEach($scope.units,function(val,key){
$scope.getUnitData(val);
};
console.log($scope.data); // Need to show all the data but currently it does not as the for each loop didn't complete
};
The service is defined as.
app.factory('service',function($http){
return {
getUnitData : function(unit){
return $http({
url : myURL,
method : 'GET',
params : {'unit' : unit}
});
}
}
});
How do I receive a callback when all the pulling has been done in the for loop ?

The result of your $http(...) call is a promise. This means you can use $q.all to wait for an array of them to complete.
$scope.updateAllUnits = function(){
$scope.data = null ; //remove existing data
var promises = [];
angular.forEach($scope.units,function(val,key){
promises.push($scope.getUnitData(val));
});
$q.all(promises).then(function success(data){
console.log($scope.data); // Should all be here
}, function failure(err){
// Can handle this is we want
});
};

Related

AngularJS Promises: Put promise in Factory so it is globally accessible and *editable*

I'm trying to pull data from an external JSON file and display it for the user to see. Through various actions, the user would then be able to change the data returned from the JSON file, without writing those changes to the file (in this example, incrementing values by one by clicking on a div). I've created a promise service that successfully pulls the data and displays it. I can even get it so the data can be changed in individual controllers.
This is where I get stuck: I cannot find a way to make any changes to the data in the PromiseService, so changes cannot propagate globally. How do I make it that any change in the promise data at the controller level will be reflected in the PromiseService and, thus, reflected in any data binding in the app? I'm new to promises, so I'm open to a completely different approach.
Plunker
HTML:
<body ng-app="pageApp" ng-controller="pageCtrl" nd-model="items">
{{items}}
<div class="button" ng-controller="buttonCtrl" ng-click="incrementValues()">
Click to increment:
<br>{{items}}
</div>
</body>
PromiseService:
pageApp.factory('PromiseService', function($http) {
var getPromise = function() {
return $http.get('items.json').then(function(response) {
return response.data;
});
};
return {
getPromise: getPromise
};
});
Button Controller (Page Controller in Plunker):
pageApp.controller('buttonCtrl', function($scope, PromiseService) {
$scope.incrementValues = function()
{
PromiseService.getPromise().then(function(data) {
$scope.items = data;
for(var i = 0; i < data.items.length; i++)
{
data.items[i]['value']++;
}
}).catch(function() {
});
};
});
The incrementValues function works successfully the first time, but each consecutive click re-pulls the promise and resets the data. To sum up: how do I reflect the incremented values in the PromiseService, as opposed to local variables?
You could add to your factory a private property where you store the items. Then create 3 different methods to update and access to that property.
pageApp.factory('PromiseService', function($http) {
var items = {}; // [] in case it is an array
var updateData = function(updatedData){
items = updatedData;
}
var getUpdateData = function(){
return items;
}
var getPromise = function() {
return $http.get('items.json').then(function(response) {
items = response.data;
return response.data;
});
};
return {
getPromise: getPromise,
updateData : updateData,
getUpdateData : getUpdateData
};
});
pageApp.controller('buttonCtrl', function($scope, PromiseService) {
$scope.items = [];
//You should call this method to retrieve the data from the json file
$scope.getData = function(){
PromiseService.getPromise().then(function(data) {
$scope.items = data;
}).catch(function() {
});
}
$scope.incrementValues = function(){
for(var i = 0; i < $scope.items.length; i++){
$scope.items[i]['value']++;
}
PromiseService.updateData($scope.items); //This could be skipped in case you do not want to 'store' these changes.
};
});
Then in others controller you could use the same service to retrieve the updated Data like this:
$scope.items = PromiService.PromiseService();
In the future you could also create a new method to update the json itself instead of stored internally
Your function creates a new $http call every time it's called, and thus returns a new promise, encspsulating new data, every time it's called.
You need to return the same promise every time:
var thePromise = $http.get('items.json').then(function(response) {
return response.data;
});
var getPromise = function() {
return thePromise;
};

AngularJS Function call looses object scope

I have a URL with query params as below.
http://somehost/page.html#/?param1=value1&param2=value2`
Inside the JS file I have a controller which defines a function as per the below code and makes a http call and assignes the data to the $scope.
angular.module('ngApp').controller('Cntrl', function ($scope,$location,$http){
var val1 = $location.search()['param1'];
$http.get('/api/call').success(function(data) {
$scope.empList = data;
console.log ($scope.empList) ; // return data
});
console.log ($scope.empList) ; // return undefined when accessed with above url
$scope.fetch = function() {
// some code which uses $scope.empList here
}
$scope.fetch();
}
I have just started to learn angularJS and no idea why $scope.empList is undefined outside the http block.
Your HTTP request is asynchronous. So you need to do like this:
angular.module('ui.kpi.bugs').controller('BugKpiForm', function ($scope,$location,$http){
var val1 = $location.search()['param1'];
$scope.empList = [];
$http.get('/api/call').success(function(data) {
$scope.empList = data;
console.log ($scope.empList) ; // return data
$scope.fetch(); // invoke only when empList data is available.
});
$scope.fetch = function() {
// some code which uses $scope.empList here
}

Controlling order of execution in angularjs

I have inherited an angular app and now need to make a change.
As part of this change, some data needs to be set in one controller and then used from another. So I created a service and had one controller write data into it and one controller read data out of it.
angular.module('appRoot.controllers')
.controller('pageController', function (myApiService, myService) {
// load data from API call
var data = myApiService.getData();
// Write data into service
myService.addData(data);
})
.controller('pageSubController', function (myService) {
// Read data from service
var data = myService.getData();
// Do something with data....
})
However, when I go to use data in pageSubController it is always undefined.
How can I make sure that pageController executes before pageSubController? Or is that even the right question to ask?
EDIT
My service code:
angular.module('appRoot.factories')
.factory('myService', function () {
var data = [];
var addData = function (d) {
data = d;
};
var getData = function () {
return data;
};
return {
addData: addData,
getData: getData
};
})
If you want your controller to wait untill you get a response from the other controller. You can try using $broadcast option in angularjs.
In the pagecontroller, you have to broadcast your message "dataAdded" and in the pagesubcontroller you have to wait for the message using $scope.$on and then process "getData" function.
You can try something like this :
angular.module('appRoot.controllers')
.controller('pageController', function (myApiService, myService,$rootScope) {
// load data from API call
var data = myApiService.getData();
// Write data into service
myService.addData(data);
$rootScope.$broadcast('dataAdded', data);
})
.controller('pageSubController', function (myService,$rootScope) {
// Read data from service
$scope.$on('dataAdded', function(event, data) {
var data = myService.getData();
}
// Do something with data....
})
I would change your service to return a promise for the data. When asked, if the data has not been set, just return the promise. Later when the other controller sets the data, resolve the previous promises with the data. I've used this pattern to handle caching API results in a way such that the controllers don't know or care whether I fetched data from the API or just returned cached data. Something similar to this, although you may need to keep an array of pending promises that need to be resolved when the data does actually get set.
function MyService($http, $q, $timeout) {
var factory = {};
factory.get = function getItem(itemId) {
if (!itemId) {
throw new Error('itemId is required for MyService.get');
}
var deferred = $q.defer();
if (factory.item && factory.item._id === itemId) {
$timeout(function () {
deferred.resolve(factory.item);
}, 0);
} else {
$http.get('/api/items/' + itemId).then(function (resp) {
factory.item = resp.data;
deferred.resolve(factory.item);
});
}
return deferred.promise;
};
return factory;
}

AngularJS : controlling services data with factory

I am trying to 'control' data in a factory from two separate controllers and while the below code works fine, it doesn't always work. For whatever reason it only binds the data about 50% of the time. Is there a way to make sure that the data is being binded all the time and if either controllers make edits to the data that the changes will be reflected on both ends.
Service:
angular.module('service', ['ngResource']).
factory('Service', function($resource){
return $resource('/api/data/:id', {id:'#id'}, {});
});
Factory:
angular.module('factory', ['ngResource', 'service']).
factory('Factory', function($resource, Service) {
this.mydata = '';
return {
getData: function(id){
return Service.query({id: id});
},
data: function(data){
return this.mydata;
},
setData: function(data){
this.mydata = data;
}
}
});
Controller:
$scope.Factory = Factory;
var items = Factory.getData(0);
items.$promise.then(function(itemArr){
var item = itemArr[0];
$scope.data = item;
Factory.setData(item);
});
If there is a better way to do this so that I don't have to set the data in the factory that would be nice. It would also be nice to not have to deal with the promise object in the controller, but I don't think it would be possible to get the data out in the factory.
After setting the data using the above factory I access it in a different controller with the following code:
var item = Factory.data();
$scope.selected = [{foo:'bar'},{foo1:'bar1'}];
angular.forEach($scope.selected, function(value, key){
item.requirements.push(value);
})
Factory.setData(item);
Ultimately I want to be able to access the same changing data from both controllers. The above works, but only some of the time and I'm not sure whats not getting set.
EDIT:
I was able to get it to work all the time by using the $scope.$watch functionality in the controller on the call back function. The data is bound, but angular needs to know what to watch for as suggested in the answer below.
To not have to manually set the data in the 'factory' (aka angular service), you can just set it in the callback to the resource:
return {
getData: function(id){
return Service.query({id: id}, function(data){
myData = data;
});
},
If you want to not deal with the promise object, you can send in a callback of your own to the getData function and when it is complete, call the callback you send in in the callback of the resource:
return {
getData: function(id, cb){
return Service.query({id: id}, function(data){
myData = data;
if (cb) {cb(data);}
});
},
Which changes the way you call getData to this:
var items = Factory.getData(0, function(itemArr){
var item = itemArr[0];
$scope.data = item;
});

How to use data from $http in other controller

How can I use the totalResults outside of the function that Im setting it? I just cant wrap my head around how to do it, I need to use the totalResults that I gather from my database and use in another function to calculate the amount of pages. I do this so I dont load all the data to the client but I still need to know the total count of rows in the database table.
My json looks like:
Object {total: 778, animals: Array[20]}
Angular:
var app = angular.module('app', []);
app.controller('AnimalController', ['$scope', 'animalSrc', function($scope, animalSrc)
{
$scope.animals = [];
var skip = 0;
var take = 20;
var totalResults = null;
//$scope.totalResults = null;
$scope.list = function()
{
animalSrc.getAll(skip, take, function(data) {
$scope.animals = $scope.animals.concat(data.animals);
// I need to be able to use this outside of function ($scope.list)
totalResults = data.total;
//$scope.totalResults = data.total;
});
};
$scope.showMore = function()
{
skip += 20;
$scope.list();
};
$scope.hasMore = function()
{
//
};
// Outputs null, should be the total rows from the $http request
console.log(totalResults);
}]);
app.factory('animalSrc', ['$http', function($http)
{
// Private //
return {
getAll: function(skip, take, callback)
{
$http({
method: 'GET',
url: 'url' + skip + '/' + take
}).
success(function(data) {
callback(data);
}).
error(function(data) {
console.log('error: ' + data);
});
}
};
}]);
You need to start thinking asynchronously. Your console.log is called before the $http has returned and totalResults has been set. Therefore, totalResults will always be null.
You need to find some way to delay the call to console.log so that the $http call can finish before you run console.log. One way to do this would be to put the console.log call inside your callback function so that it is definitely called after $http's success.
A more elegant way to do this is to use promises. angular.js implements $q, which is similar to Q, a promise library.
http://docs.angularjs.org/api/ng.$q
Instead of creating a callback function in getAll, you return a promise. Inside $http success, you resolve the promise with the data. Then, in your controller, you have a function that is called when the promise is resolved. Promises are nice because they can be passed around and they allow you to control the flow of your asynchronous code without blocking.
Here's a boilerplate I was just working on for myself for similar setup where data is an object that needs to be split into more than one scope item. Issue you weren't grasping is storing the data within the service, not just using service to retrieve data. Then the data items are available across multple controllers and directives by injecting service
app.run(function(MyDataService){
MyDataService.init();
})
app.factory('MyDataService',function($http,$q){
var myData = {
deferreds:{},
mainDataSchema:['count','items'],
init:function(){
angular.forEach(myData.mainDataSchema,function(val,idx){
/* create deferreds and promises*/
myData.deferreds[val]=$q.defer();
myData[val]= myData.deferreds[val].promise
});
/* load the data*/
myData.loadData();
},
loadData:function(){
$http.get('data.json').success(function(response){
/* create resolves for promises*/
angular.forEach(myData.mainDataSchema,function(val,idx){
myData.deferreds[val].resolve(response[val]);
});
/* TODO -create rejects*/
})
}
}
return myData;
})
app.controller('Ctrl_1', function($scope,MyDataService ) {
$scope.count = MyDataService.count;
$scope.items =MyDataService.items;
});
app.controller('Ctrl_2', function($scope,MyDataService ) {
$scope.items =MyDataService.items;
$scope.count = MyDataService.count;
});
Plunker demo

Resources