I have an Angular service that updates the $rootScope. The updating actually works fine, but it throws an error in the console which worries me.
app.service("scroll", function($rootScope, $window) {
this.scrolling = function(delta){
$rootScope.scroll.current -= delta;
}
$rootScope.$apply();
});
If I remove the $rootScope.$apply() the error doesn't appear, but then the rootScope value doesn't seem to be updated when I refer to it in my HTML.
For example in my HTML:
{{scroll.current}}
This only updates when I use $rootScope.$apply(). Is there a better way to update the $rootScope, or am I just doing something wrong?
Error being thrown:
Error: [$rootScope:inprog] http://errors.angularjs.org/1.3.0-rc.5/$rootScope/inprog?p0=%24apply
As $rootScope.$apply() trigger a $digest and in angular at any point in time there can be only one $digest or $apply operation in progress.
Use $timeout.
app.service("scroll", function($rootScope, $window, $timeout) {
this.scrolling = function(delta) {
$rootScope.scroll.current -= delta;
}
$timeout(function() {
$rootScope.$apply(); //this triggers a $digest
}, 1);
});
Probably your function:
$rootScope.$apply();
is called also when it is not needed.
You just have to add this line:
if ( !$rootScope.$$phase )
$rootScope.$apply()
$$phase is when angular is digesting
EDIT: corrected $rootScope.apply() to $rootScope.$apply()
Related
I have a controller without $scope
angular.module('todoApp', [])
.controller('TodoListController', function() {
var todoList = this;
todoList.title = "Default title";
setTimeout(function() {
todoList.title = "Another title will appear after 5 seconds";
}, 5000);
// ...some magic here
});
And view:
<h1>Current title: {{TodoListController.title}}</h1>
This code won't works correctly, becaufe function in setTimeout won't run $digest() which can update my TodoListController.title.
I know I can use $scope and use $scope.$digest(). But - is it possible to run $digest() without it? I have always access to object angular. Maybe through this object?
You should use $timeout instead of vanilla setTimeout.
angular.module('todoApp', [])
.controller('TodoListController', function($timeout) {
var todoList = this;
todoList.title = "Default title";
$timeout(function() {
todoList.title = "Another title will appear after 5 seconds";
}, 5000);
// ...some magic here
});
Using $timeout from angular will handle starting digest cycle.
Angulars $timeout is also useful if you want to notify angular to do updates without delay. In this case you can invoke it without second parameter.
$timeout(function(){
//something outside angular ...
});
Function passed to $timeout will be invoked on next digest cycle.
This way is better than calling $digest manually because it will prevent digest already in progress errors.
You must use the angular version of timeout: $timeout.
https://docs.angularjs.org/api/ng/service/$timeout
This service trigger the angular $digest process.
i used setInterval() function in angular.js controller to show timer on UI later. create a scope variable which have value of inactivity in seconds.
setInterval(function(){ scope.timer++ }, 1000);
what it suppose to do is, it should update screen every second. but it doesn't. some time it update screen after two seconds, three seconds and sometime it update it every seconds, but its not consistent.
while when I log scope. timer value it behaves properly.
what would be the problem of this. Does angular.js restrict native javascript somehow or what?
Better is to use $interval, example:
var app = angular.module('MyApp', []);
app.controller('AppCtrl', function($scope, $interval) {
$scope.timer = 0;
$interval(function() { $scope.timer++; }, 1000);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script>
<div ng-controller="AppCtrl" ng-cloak="" ng-app="MyApp">
<div ng-bind="timer"></div>
</div>
May be the value is updating in the controller but not in the DOM
try this
JS
setInterval(function(){
scope.timer++
setTimeout(function(){
$scope.$apply();
}, 1);
}, 1000);
Or you can use $interval and you are following my answer you can use $timeout instead of setTimeout also
You should use $interval
Update your code to this
$interval(function(){
$scope.timer++;
}, 1000)
Make sure that your $scope.timer is correctly initialized
You could try using Angular's $interval, from the docs
You have to add it to your controller and use it, like this ( view Plunker )
app.controller('testController', ['$scope', '$interval', function($scope, $interval) {
var count = 1;
$scope.count = count;
$interval(function() {
$scope.count++;
}, 1000);
}]);
Use scope.$apply
setInterval(function(){ scope.timer++; scope.$apply(); }, 1000);
setInterval is not angular function so scope is not refreshing.
Or use angular service ( prefered solution ):
$interval(function(){ scope.timer++;},1000);
setInterval is a JS native function, to execute it inside the digest loop you should call the apply() method or alternatively use the wrapper $interval as suggested in the AngularJS doc.
$interval(function(){ scope.timer++ }, 1000);
https://docs.angularjs.org/api/ng/service/$interval
setInterval is not an angular function, hence the scope is not refreshed, I DO NOT RECOMMEND using scope.$apply() as its wasteful.
use angular's inbuilt $interval method:
.controller('ExampleController', ['$scope', '$interval',
function($scope, $interval) {
stop = $interval(function() {
$scope.timer++;
}, 1000);
});
documentation: https://docs.angularjs.org/api/ng/service/$interval
this is my controller:
.controller('mainController', function($http, $scope, $timeout, $sce, updateService) {
updateData = function() {
updateService.getDataA($http, $scope);
updateService.getDataB($http, $scope, $sce);
}
var updateIntervalId = setInterval(updateData, 1000);
})
Now when the user refreshes the page old requests are still being made, and I tried to cancel it by putting this under the interval call:
window.onbeforeunload = function() {
console.log('interval killed');
clearInterval(updateIntervalId);
}
the console is logging 'interval killed', so its being executed, but requests are still being made, why?
Let us do the angular way
Inject $interval service in your controller
And update your code to following
var intervalPromise = $interval(updateData, 1000);
$scope.$on('$destroy', function() { // Gets triggered when the scope is destroyed.
$interval.cancel(intervalPromise );
});
And remove window.onbeforeunload event handler.
When it comes to angularJS I would always recommend to use $interval to handle intervals, since it works better with the angularJS hidden mechanics.
Then if the event window.onbeforeunload lies outside the controller, you will never be able to access updateIntervalId since it lies inside another scope, hence you should make it global.
In AngularJS, everywhere I read it says that you almost rarely ever call $apply() yourself, as it's a bad idea somehow (usually left unclear.)
Can someone explain to me:
How to avoid using $apply() but still updating variables in $scope
Why is it so terribly bad to call $apply()
And how to determine when it is OK to use $apply()
Example code:
app.controller('ExampleController', function($scope) {
$scope.variableForView = 'Initial value';
// for demonstration purposes
someEvent.on('eventFire', function(data) {
$scope.variableForView = data;
$scope.$apply(); // ???
});
});
Basically, $apply runs the $digest. If $digest is already running, then you can't initiate another $digest.
I have created the method $safeApply(), that I add to the prototype of my $rootScope inside the run() commend:
// $safeApply only calls $apply if $digest is not running
// Otherwise, will only call back fn()
Object.getPrototypeOf($rootScope).$safeApply = function(fn)
{
var phase = $rootScope.$$phase;
if (phase == '$apply' || phase == '$digest') {
if (_.isFunction(fn)) fn();
} else {
this.$apply(fn);
}
};
This basically makes sure you are not already in the $digest cycle before calling $apply. If you are (which means it is not necessary to call $apply, I just run the the fn. I never use $apply() and always use $safeApply().
Use the binding methods angular provides. Then it will do the $apply for you. The reason they say manually $applying is bad is because you should be using the angular methods for binding and if you are not using them and having to $apply manually, then your doing something wrong.
app.controller('ExampleController', function($scope) {
$scope.variableForView = 'Initial value';
// for demonstration purposes
$scope.$on('someEventFire', function(data) {
$scope.variableForView = data;
});
});
Also see https://github.com/angular/angular.js/wiki/When-to-use-%24scope.%24apply%28%29
Reading this excellent book, Mastering Web Development in AngularJS, I ran across this code:
var Restaurant = function ($q, $rootScope) {
var currentOrder;
this.takeOrder = function (orderedItems) {
currentOrder = {
deferred:$q.defer(),
items:orderedItems
};
return currentOrder.deferred.promise;
};
this.deliverOrder = function() {
currentOrder.deferred.resolve(currentOrder.items);
$rootScope.$digest();
};
this.problemWithOrder = function(reason) {
currentOrder.deferred.reject(reason);
$rootScope.$digest();
};
My understanding is that the $rootScope.$digest(); calls are made in order to alert Angular that the Promise's state has been updated.
Is my understanding correct? Also, is it necessary to make the above $rootScope.$digest(); calls?
$scope.$digest() is what processes all of the $watch events that are on the current and children scope. It essentially manually tells the scope to check if a scope variable has changed. You don't generally want to use this when you are inside of a controller or a directive, because the $scope.$apply() function calls the $digest anyway and it is called when you mutate a scope variable.
Checkout this link for an example.
You don't need a $rootScope.$digest here because resolving/rejecting the promise will fire a $rootScope.$digest internally, as $interval,$timeout,and $http (after request finished) do that for you. And this $digest can throw errors of $digest already in progress.