How to digest changes in method outside angular and outside scope - angularjs

So I realized that doing changes outside the angular-bounds, like in an async AJAX callback, will not make angular call $apply on the scope. So far I have solved these issues by calling $scope.$apply() after changes have been made, but this time the function is not located in the same scope. The method lies in a factory, and is called from a controller.
I have tried sending the scope as a parameter from the controller to the factory, which does in fact work, but is not pretty as I expect to have many methods like this, and sending an additional parameter each time seems ugly.
Any ideas?

Related

Angular doesn't update the view

I have worked with angular for long time without facing many problems until now. I have a controller which calls a function when a ng-change event occurs. I have a set of different reports that are generated depending on some select filters defined in the main controller called Principal (PrincipalController).
Most of the controllers work fine and generate what they suppose, however with the last one (AnalisisEvaluacionController) I am struggling.
Its view is loaded correctly in the beginning. I print the name in the view but when I called the method from a ng-change event the name does not change. I also have a ng-repeat over $scope.notas which does not work. Actually that is the real issue. I have read about it and I have tried with
$scope.$apply() and $scope.$digest() but the error
angular.js:14800 Error: [$rootScope:inprog] $apply already in progress is thrown
I have created a snippet with the code https://bitbucket.org/snippets/jorguerra/Ee6G6y
I basically have a ng-repeat over evaluacion.notas which is reloaded when the function generarInforme is call, which call a factory that brings a JSON back which is assigned to $scope.evaluacion in the controller. The problem is that I can iterate or show the data I need to display
You don't need to call $scope.$apply (or $digest) in ng-change handler, as it's handler is already wrapped in $scope.$apply block. If you, however need to call $scope.$apply (or $digest) in some situations (e.g. your ng-change handler does some asynchronous operation after which you need to sync the view model), you can prevent the error this way:
(scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn);
You can also wrap your fn in $timeout block
$timeout(fn)
which safely applies $scope.$apply.
I could solve my problem by creating a function init inside of AnalisisEvaluacionController which calls the method generarInforme and changing set all the required variables.

How to retain scope data while traversing between various controllers

I have a download function, which has a promise called onProgress(which returns download percentage), I want to go to some other view and come back. Even though controller's scope is with proper data the page is not getting refreshed with new data.
Even with this vague description it sounds like the onProgress() function is not in "angular world". Therefore, any changes to $scope that you make in the promise callback will not be noticed by angular until the next digest cycle.
The solution is easy: inside the onProgress() promise callback, wrap your changes to $scope in $apply:
onProgress().then(function(percent)
{
$scope.$apply(function() {
// your changes to $scope here, so that angular notices them
}
}

Angular $scope.$digest vs $scope.$apply

I just want to know how to use $digest. Inside a controller the following code works fine and it updates the DOM after 3 seconds:
setTimeout(function(){
$scope.$apply(function(){
$scope.name = 'Alice';
});
}, 3000);
However by using
setTimeout(function(){
$scope.$digest(function(){
$scope.name = 'Alice';
});
}, 3000);
nothing happens...
I thought that they do the same thing. Am I wrong?
$apply() and $digest() have some similarities and differences. They are similar in that they both check what's changed and update the UI and fire any watchers.
One difference between the two is how they are called. $digest() gets called without any arguments. $apply() takes a function that it will execute before doing any updates.
The other difference is what they affect. $digest() will update the current scope and any child scopes. $apply() will update every scope. So most of the time $digest() will be what you want and more efficient.
The final difference which explains why $apply() takes a function is how they handle exceptions in watchers. $apply() will pass the exceptions to $exceptionHandler (uses try-catch block internally), while $digest() will require you handle the exceptions yourself.
I think you must go through documents $apply
$apply() is used to execute an expression in angular from outside of
the angular framework
Usually, you don't call $digest() directly in controllers or in
directives. Instead, you should call $apply() (typically from within a
directive), which will force a $digest().
Also as suggest by Jorg, use $timeout
In angularjs $scope object is having different functions like $watch(), $digest() and $apply() and we will call these functions as central functions.
The angularjs central functions $watch(), $digest() and $apply are used to bind data to variables in view and observe changes happening in variables.
Generally in angularjs we use $scope object to bind data to variables and use that variables values wherever we required in application. In angularjs whatever the variables we assigned with $scope object will be added to watch list by using $scope.$watch() function.
In angularjs once variables added to watch list $scope.digest() function will iterates through the watch list variables and check if any changes made for that variables or not. In case if any changes found for that watch list variables immediately corresponding event listener function will call and update respective variable values with new value in view of application.
The $scope.$apply() function in angularjs is used whenever we want to integrate any other code with angularjs. Generally the $scope.$apply() function will execute custom code and then it will call $scope.$digest() function forcefully to check all watch list variables and update variable values in view if any changes found for watch list variables.
In most of the time angularjs will use $scope.watch() and $scope.digest() functions to check and update values based on the changes in variable values.
A better understanding of $watch, $apply, and $digest is provided in this article.
Simply,
$watch that is used to detect changes in the UI
$apply would tell the $digest loop what changes should be applied
$digest loop would run and will ask every $watch for changes, the DOMs would change accordingly for what has what has been applied
You should call $apply only.

angularjs: $apply(fn) better than $apply()

I am reading
http://angular-tips.com/blog/2013/08/watch-how-the-apply-runs-a-digest/
directive link function:
element.bind('click', function() {
scope.foo++;
scope.bar++;
scope.$apply();
});
a better way for using $apply:
element.bind('click', function() {
scope.$apply(function(){
scope.foo++;
scope.bar++;
});
});
What’s the difference? The difference is that in the first version, we are updating the values outside the angular context so if that throws an error, Angular will never know. Obviously in this tiny toy example it won’t make much difference, but imagine that we have an alert box to show errors to our users and we have a 3rd party library that does a network call and it fails. If we don’t wrap it inside an $apply, Angular will never know about the failure and the alert box won’t be there.
Confusion:
Why angular need to know error, i just need to show it for users. for example, there is an ajax request in link fn of directive, I just need to tell what happened if fails.
TAngular $scope has a function called $apply() which takes a function as an argument. AngularJS says that it will know about model mutation only if that mutation is done inside $apply(). So you simply need to put the code that changes models inside a function and call $scope.apply(), passing that function as an argument. After the $apply() function call ends, AngularJS knows that some model changes might have occurred. It then starts a digest cycle by calling another function —- $rootScope.$digest() — which propagates to all child scopes. In the digest cycle watchers are called to check if the model value has changed. if a value has changed, the corresponding listener function then gets called. Now it’s upto the listener how it handles the model changes.
The Ajax call through Angular buildin $http the model mutation code is implicitly wrapped withing $apply() call, so you don’t need any additional steps.

Angular $scope.$apply vs $timeout as a safe $apply

I'm trying to better understand the nuances of using the $timeout service in Angular as a sort of "safe $apply" method. Basically in scenarios where a piece of code could run in response to either an Angular event or a non-angular event such as jQuery or some standard DOM event.
As I understand things:
Wrapping code in $scope.$apply works fine for scenarios where you
aren't already in a digest loop (aka. jQuery event) but will raise an error if a digest is in progress
Wrapping code in a $timeout() call with no delay parameter works whether already in a digest cycle or not
Looking at Angular source code, it looks like $timeout makes a call to $rootScope.$apply().
Why doesn't $timeout() also raise an error if a digest cycle is already in progress?
Is the best practice to use $scope.$apply() when you know for sure that a digest won't already be in progress and $timeout() when needing it to be safe either way?
Is $timeout() really an acceptable "safe apply", or are there gotchas?
Thanks for any insight.
Looking at Angular source code, it looks like $timeout makes a call to
$rootScope.$apply().
Why doesn't $timeout() also raise an error if a digest cycle is already in progress?
$timeout makes use of an undocumented Angular service $browser. Specifically it uses $browser.defer() that defers execution of your function asynchronously via window.setTimeout(fn, delay), which will always run outside of Angular life-cycle. Only once window.setTimeout has fired your function will $timeout call $rootScope.$apply().
Is the best practice to use $scope.$apply() when you know for sure that a digest won't already be in progress and $timeout() when needing it to be safe either way?
I would say so. Another use case is that sometimes you need to access a $scope variable that you know will only be initialized after digest. Simple example would be if you want to set a form's state to dirty inside your controller constructor (for whatever reason). Without $timeout the FormController has not been initialized and published onto $scope, so wrapping $scope.yourform.setDirty() inside $timeout ensures that FormController has been initialized. Sure you can do all this with a directive without $timeout, just giving another use case example.
Is $timeout() really an acceptable "safe apply", or are there gotchas?
It should always be safe, but your go to method should always aim for $apply() in my opinion. The current Angular app I'm working on is fairly large and we've only had to rely on $timeout once instead of $apply().
If we use $apply heavily in the application, we might get the Error: $digest already in progress. It happens because one $digest cycle can be run at a time. We can resolve it by $timeout or by $evalAsync.
The $timeout does not generate error like "$digest already in progress“ because $timeout tells Angular that after the current cycle, there is a timeout waiting and this way it ensures that there will not any collisions between digest cycles and thus output of $timeout will execute on a new $digest cycle.
I tried to explain them at : Comparison of apply, timeout,digest and evalAsync.
May be it will help you.
As far as I understand it, $timeout is a wrapper around setTimeout which implicitly calls $scope.$apply, meaning it runs outside of the angular lifecycle, but kickstarts the angular lifecycle itself. The only "gotcha" I can think of is that if you're expecting your result to be available this $digest, you need to find another way to "safe apply" (which, AFAIK, is only available via $scope.$$phase).

Resources