Disclaimer: I'm a beginner with AngularJS.
I'm trying to update $scope after a promise has returned, but it does not work. Here's the simplified code:
.controller('ChooseRewardCtrl', ['$scope', ...,
function($scope, ...) {
// scope data
$scope.points = CardModel.points;
// function called to refresh points
$scope.refresh_points = function() {
return CardModel.refresh_points() // This is a promise that changes CardModel.points value
.then(function(data){
$scope.$apply() // Throw error (cf. above)
})
}
}
I get a Error: [$rootScope:inprog] $digest already in progress
I saw lots of post about this error, but nothing that I can directly link to my problem.
Thanks!
Try this:
.then(function(data){
$scope.points = CardModel.points;
})
you need not use $apply().
log your function parameter ('data') and see what does it contain.
It should contain value returned by promise.
assign the appropriate value to your scope variable.
Related
I'm an angular beginner and I'm trying to set up a factory that gets data from my php back-end and updates the controller. Here is my basic code:
factory
app.factory('sessionStatus', ['$http', function($http){
return {
status:function(){
var status = {};
$http.get('/game_on/api/api.php?session-status')
.then(function(response){
angular.copy(response.data, status);
});
return status;
}
};
}]);
controller
app.controller('headerCtrl', ['$scope', 'sessionStatus', function($scope, sessionStatus){
$scope.sessionStatus = sessionStatus.status();
console.log($scope.sessionStatus);
}]);
This gives me the following in the console:
Object{}
session:"false"
However, when I adjust the controller to log the value of the session property like so:
console.log($scope.sessionStatus.session);
I simply get undefined in the console.
What am I doing wrong?
EDIT
For anyone else who suffers from my distinct brand of retardation, here's the conclusion I've reached:
The main cause of the issue is that I've been trying to access asynchronously generated information returned by the $http service before its promise has been resolved (i.e. before .then() is finished executing), which is leading to undefined being logged because the data has literally not yet been defined since the request that generates it is still in progress by the time console.log() is called in the controller.
Using the $timeout service I was able to delay the calling of console.log(), which resulted in the data being correctly logged to the console.
$timeout(function(){
console.log($scope.sessionStatus.session);
}, 2000); // logs "false" to the console like originally expected
My previous thinking was that I should make the call to my factory method as short as possible within the controller, and then act upon its results outside of that call. This was obviously a bad idea because .then() exists so that you can act on the results of an asynchronous request at the precise moment the request is completed.
In hindsight this all seems so obvious, but hopefully this explanation helps someone else in the future...
Thanks for all the answers I received, they were a big help!
There are a couple of changes that you can make to the code here. First I would suggest resolving the promise and assigning the value in the controller instead of in the service. Next, you won't need to use Angular.Copy on the result object, you can simply make a variable assignment. Try something like simply this, then build out:
app.factory('sessionStatus', ['$http', function($http){
return {status: $http.get('/game_on/api/api.php?session-status')}
}]);
and
app.controller('headerCtrl', ['$scope', 'sessionStatus', function($scope, sessionStatus){
sessionStatus.status.then(function(result){
$scope.sessionStatus = result.data; //or some property on result
});
}]);
since javascript is asynchronous it does't wait until http response and return the status. thats why it return null. try something like this
app.factory('sessionStatus', ['$http', function($http){
return {
status:function(){
$http.get('/game_on/api/api.php?session-status')
}
};
}]);
app.controller('headerCtrl', ['$scope', 'sessionStatus', function($scope, sessionStatus){
$scope.sessionStatus = sessionStatus.status().then(function(response) {
console.log(response);
});
}]);
Try to return a promise from your factory:
app.factory('sessionStatus', ['$http', function($http) {
var factory = {
status: status
};
return factory;
function status() {
return $http.get('/game_on/api/api.php?session-status');
}
}]);
Then in controller:
app.controller('headerCtrl', ['$scope', 'sessionStatus', function($scope, sessionStatus) {
function getSuccess(response) {
$scope.sessionStstus = response.data;
}
function getError(response) {
console.log('error');
}
sessionStatus.status()
.then(getSuccess)
.catch(getError);
console.log($scope.sessionStatus);
}]);
$I have a custom javascript object, that can fire events.
I would like to access the angular $scope inside the event-handler, but I have read somewhere that using angular.element(...).scope() is not good, because it's only meant for testing.
My other idea was to register the handle on my object inside the controller, but this is not working (looks like $scope.somevalue gets set, but I don't think $scope is the same object).
I have found many answers here on Stack Overflow for similar questions, but they all seem to be using directives. All I want is to get a value from the object when it's updated, and display it.
Here are the two ways I have tried.
var myObj = GetMyObjInstance();
// Working, but apparently it's not good practise to call .scope() on an element.
myObj.onUpdated = function(){
console.log("myObj updated");
var v = myObj.getValue();
var controllerDiv = document.getElementById("controller");
var $scope = angular.element(controllerDiv).scope();
$scope.apply(function(){
$scope.someValue = v;
});
}
// Tried to do this, thinking i would get closure on the scope.
angular.module('myApp', []).controller('controller', function($scope){
myObj.onUpdated = function(){
console.log("myObj updated"); // Gets logged to console...
var v = myObj.getValue();
$scope.somevalue = v; // ... but somevalue does not get displayed.
$scope.apply(); // Error, says it's not a function, so maybe this is not the right object?.
}
});
Use AngularJS directives to handle events and update scope.
app.directive("xdEvent", function() {
return linkFn(scope, elem, attrs) {
elem.on("event", function(e) {
scope.$eval(attrs.xdEvent, {$event: e});
scope.$apply();
});
};
};
USAGE
<div xd-event="fn($event)"></div>
I think using a Service instead of a controller is a better practice. You can call a service from outside javascript with the injector like explained in this thread :
Call angularjs service from simple js code
If it is still important for you to access this variables from controller, you can use $watch to tell your controller to update itself when the service variables change.
Hope this help.
A+
I'm fetching the data using $http.get() and passing it as argument to a custom filter to get the filterd data. But it is giving an
error: $digest already in progress.
Does anyone know how to avoid this error or achieve the same thing but a different way?
var map=angular.module('map', [])
.controller('mapCtrl', function ($scope,$filter,$http) {
$http.get('./json/integrated.json').success(function(data) {
$scope.sitesInfo = data;
});
var filteritems= $filter('applySfotwareFilter')($scope.sitesInfo);
});
I think you should place
var filteritems= $filter('applySfotwareFilter')($scope.sitesInfo);
inside your success function;
What happens is the filtering starts before your data is ready.
My problem is I need "det" value applied to the controller and reload it.
Anyway nevermind it and continue reading first so you will understand my question.
I have this controller below.
At first load, the xxx isn't going to exist in the object then det value will be null. So it is expected that the controller's service will have an error telling that it can't be find. (See Controller code below)
However, when I click a button on my page (buttons html code is not here, I don't think it is necessary), it fills the object in and I'm wishing to reload the controller so I will see my expected output.
The HTML below is the one who loads the controller, what I'm expecting is that the
data-ng-model="{{$parent.$root.ParentItems['xxx'].xxx}}" will update the xxx value in controller. And it actually does because I'm using "<span>{{$parent.$root.ParentItems['xxx'].detnumber}}</span>" to test it.
Now, again,
My problem is I need the "det" value applied to the controller and reload it.
What I'm thinking is to create a new controller but I will just repeat the code.
//html
<div data-ng-switch-when="thisIsIt" ControllerOne data-ng-model="{{$parent.$root.ParentItems['xxx'].xxx}}"></div>
<span>{{$parent.$root.ParentItems['xxx'].xxx}}</span>
//Attribute ControllerOne
controller: function ($scope, $element, $http) {
function par() {
var xxx= null;
xxx = $scope.$parent.$root.ParentItems['xxx'].xxx;
var det = { xxx: xxx};
return det;
}
$http.post('/api/values/entries/GoHere', par()).success(function (salData) {
var buildSHGraph = function (shData) {
//code code codes...
}
$scope.Array1 = [];
angular.forEach(salData, function (evt) {
//Code Code Codes
});
buildSHGraph($scope.Array1);
});
}
I thing you can use $rootScope and pass your value to it. Then the value can be accessible globally by your application.
When your value is downloaded from ajax, the scope/html will be updated.
When you define variable with 'var xxx' cannot access it outside of the scope of this function, in your case 'par'.
function par() {
this.xxx = null;
this.xxx = $scope.$parent.$root.ParentItems['xxx'].xxx;
}
When you try to change view from callback from async task such as $http.post you need to '$digest' or '$apply' the scope
//Do this in success callback function in your ajax request
$timeout(function() { //$timeout must be setup as dependency in constructor such as $scope and $http
$scope.$digest();
});
I get the basic idea of the infinite digest loop and how it happens, but I'm running into the problem. Here is a fiddle demonstrating my code and problem:
http://jsfiddle.net/eS5e5/1/
In the jsfiddle console you'll see the infinite digest loop.
Basically I have to make decisions on data that may not have loaded yet so I need to wait for the promise to resolve using then(). I have a promise called user. There are two different places in the code where I call then() on user.
Just after I define it. I need to set a scope variable based on it.
In another scope method, $scope.isAdmin()
For number 2, it might be asked why I just don't use $scope.user directly in the $scope.isAdmin() method. The problem is, it's possible for $scope.isAdmin() to be called before the async request for the user returns, in which case I need to 'block' before returning from $scope.isAdmin().
My question is, what about $scope.isAdmin() is making angular think that a 'watched' variable has changed and that the digest cycle needs to run again?
$scope.isAdmin() isn't actually changing anything.
Here is the stripped down code:
HTML:
<body ng-controller='myController'>
<div ng-if='isAdmin()'>Hi! <strong>{{ user.username }}</strong> is an Admin!!!</div>
<div ng-if='!isAdmin()'>Hi! <strong>{{ user.username }}</strong> is NOT an Admin!!!</div>
</body>
And the JS:
angular.module('myApp', [])
.factory('myService', function($q, $timeout) {
return {
getUser: function() {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve({ username: 'me', isAdmin: true });
}, 2000);
return deferred.promise;
}
};
})
.controller('myController', function($scope, $q, myService) {
var getUserDeferred = $q.defer();
var user = getUserDeferred.promise;
user.then(function(user) {
$scope.user = user;
return user;
});
$scope.getUser = function() {
return myService.getUser().then(function(user) {
getUserDeferred.resolve(user);
});
};
$scope.isAdmin = function() {
return user.then(function(user) {
return user.isAdmin;
});
};
$scope.getUser();
});
So I finally figured out my own problem and thought I would answer it for others in case someone else may find this info useful.
The crux of the fix had to do with 2 concepts: angular promises and angular watches. By being aware of and applying the 2 concepts together the fix was actually pretty simple.
Everything you put on $scope is 'watched' including functions. Every time something watched changes $scope.$apply() runs again to apply the changes. If a scope function (eg: $scope.isAdmin()) changes its return value from one 'apply' to the next it will trigger another 'apply', until things stabilize and the return value isn't changing.
But in my code I was returning user.then(...) which just returns a new promise (which kept the apply cycle going on forever since the return value kept changing).
In my isAdmin() function I needed to defer its return value until the user actually loaded (any other return value would be meaningless). So I changed the code to check if the user async call had resolved by checking $scope.user and if so returning a valid isAdmin value. If $scope.user was still not defined I would just return the promise I already created.
I changed the $scope.isAdmin() to be:
$scope.isAdmin = function() {
if ($scope.user) {
return $scope.user.isAdmin;
}
return user;
};
This has the same effect as the original code without triggering an infinite apply cycle. Specifically, if the $scope.user has not resolved we still return a promise as before, by returning the user var. Note however that the user var is the same promise not a new one created by then() so the apply cycle stabilizes.
And just for completeness here is the updated jsfiddle:
http://jsfiddle.net/eS5e5/2/