AngularJS Function call looses object scope - angularjs

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
}

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;
};

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;
}

Angular Access scope data to factory?

I have a situation where i am getting data on scroll from a service. Now i need to filter data using popular data and latest post
Here is my service:
App.factory('Serviec', function ($http, $rootScope) {
var Hututoo = function () {
this.items = [];
this.busy = false;
this.after = 'Serviec_0';
};
Serviec.prototype.nextPage = function () {
if (this.busy) return;
this.busy = true;
// return undefined
console.log($rootScope.listtype);
$http.get(baseurl + 'ajax/gethome?after=' + this.after).success(function (data) {
var items = data;
for (var i = 0; i < 5; i++) {
this.items.push(items[i]);
// debugger;
}
this.after = "Hututoo_" + this.items.length;
this.busy = false;
}.bind(this));
};
return Serviec;
});
In controller:
$scope.data= new Serviec();
$scope.listtype= 'latest';
$scope.changelist = function(str){
$rootScope.listtype = str;
$scope.data.items=[];
$scope.data.after = 'Serviec_0';
$http.post(baseurl+"ajax/gethome","after="+$scope.hututoo.after+"&list="+str).success(function(data){
$scope.data.items = data;
});
}
Html
<li ng-click="expression = 'latest';changelist('latest');" ng-class="{latest_icon:expression == 'latest'}">Latest Hoot</li>
<li ng-click="expression = 'popular';changelist('popular');" ng-class="{popular_icon:expression == 'popular'}">Popular Hoots</li>
So with these click i need to order data .I have to make http call to get data according to user click.
I was thinking that i can make a scope data that define listing type and get it in factory.
How can i inject this scope in Serviec Factory. I have tried it using rootscope. initally list type is set to latest , but it shows undefined. So what would be the best method achieve this?
Update:
Now i can access scope data in angular service, but small issue comes here is on list click previous item's in scope doesn't get empty and new items get pushed into the scope.
So demand is on list click previous data become zero and new get pushed into the scope.
Pass the $rootScope to controller and set the listtype as required.
App.controller('MainCtrl', function($scope, $rootScope, Hututoo) {
$scope.hututoo = new Hututoo();
$scope.listtype= 'latest';
$scope.changelist = function(str){
$rootScope.listtype= str;
$scope.hututoo = new Hututoo();
$scope.hututoo.nextPage();
}
});
Plunker
Avoid using $rootScope -- it's bad practice, much like using the head object in pure JS. You're already able to share data between the factory and controller, so why not just make listtype a property of the factory:
var Hututoo = function () {
...
this.listtype = 'latest';
};
and use it in your controller as you are other properties:
$scope.changelist = function(str){
$scope.hututoo.listtype = str;
...
};
Demo <-- ajax requests don't work for obvious reasons
$scope is not available to inject in services however you can pass it using parameters like so.
app.factory('Hututoo', function ($resource) {
var somePrivateVar = [];
return {
set: function(scopeVar){
somePrivateVar.push(scopeVar);
},
get: function(){
return somePrivateVar;
}
}
});
then in controller
Hututoo.set($scope.anyVar);

Proper way of dealing with forEach Ajax calls in Angular

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
});
};

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