Angular Watch and ng-click sequence of events - angularjs

I have this code inside an angular directive, and I'm finding the $watch behavior a bit confusing. The updateSelect is called in an "ng-click":
scope.updateSelect = function (type) {
scope.selectionCtrl.activeList = scope.seedLists[type];
scope.selectionCtrl.activeListKey = type;
scope.selectionCtrl.activeSelection = scope.selection[type];
scope.selectionCtrl.staged = [];
scope.selectionCtrl.stageRemove = [];
if (type !== scope.activeTab) {
scope.activeTab = type;
}
console.log("update");
};
scope.$watch('selectionCtrl.activeList', function(newValue, oldValue) {
console.log("watch");
}, true);
When I click on the button (triggering updateSelect), and watch the console, I see "update" and then "watch". The first thing that happens inside the function is selectionCtrl.activeList is set, so I would expect to see "watch" and then "update".
Shouldn't watch trigger as soon as the array has changed?

The function has to finish first as javascript is single threaded.
Because the function was called via the ng-click directive, angular will run a digest cycle. Part of that digest cycle is to run through the watch list and resolve all the changes that may have occurred since the cycle last ran.
In the example you give, selectionCtrl.activeList is changed in updateSelect which subsequently results in the watch callback being called.

When does Angular execute watch callback?
It's related to $digest and $apply, and certainly it does not execute within your raw javascript code.
To make watch execute forcefully, you can run $scope.apply() manually, but may cause more problem and not necessary if it is within a angularjs function, i.e. $timeout, $interval, etc, because it will be called automatically after the function.
For more info., lookup;
How do I use $scope.$watch and $scope.$apply in AngularJS?
https://groups.google.com/forum/#!topic/angular/HnJZTEXRztk

https://docs.angularjs.org/api/ng/type/$rootScope.Scope :
The watchExpression is called on every call to $digest() and should return the value that will be watched. (Since $digest() reruns when it detects changes the watchExpression can execute multiple times per $digest() and should be idempotent.)
If you try i.e.:
scope.selectionCtrl.activeList = scope.seedLists[type];
scope.$digest();
you'll get Error: [$rootScope:inprog] $apply already in progress.

Related

Should a ngSubmit function call have a return statement in order to trigger the $digest cycle at the right time?

Say we have the following simple form:
<form ngSubmit="doSomething()">
<input type="submit" value="Submit" />
<form>
And in the controller:
var ctrl = this;
ctrl.doSomething = function () {
// Anything can happen here
}
I know the digest cycle is triggered by ngSubmit, but how does Angular know when to run the digest cycle? Is it run after doSomething() is complete, and if so how does angular know that doSomething() has indeed completed because I haven't seen any examples of ngSubmit functions using a return statement? e.g.
ctrl.doSomething = function () {
// Anything can happen here
// Add return statement so that angular knows callback is complete perhaps?
return;
}
The reason I ask is because within my own doSomething() function I'm updating a scope value e.g. ctrl.myValue++. The updated value is being updated/reflected in the model fine but I don't understand how, or rather when, angular is able to ascertain that doSomething() has finished and it's now time to safely start the digest cycle.
I know the digest cycle is triggered by ngSubmit
Yes, ngSubmit directive like ngClick triggers digest cycle
I haven't seen any examples of ngSubmit functions using a return statement?
Please read more about Scope Life Cycle
"... assignment such as $scope.username="angular" will not immediately cause a $watch to be notified, instead the $watch notification is delayed until the $digest phase. This delay is desirable, since it coalesces multiple model updates into one $watch notification as well as guarantees that during the $watch notification no other $watches are running. If a $watch changes the value of the model, it will force additional $digest cycle."
So when you call ctrl.myValue++, the model changes and it fires digest cycle
Add return statement so that angular knows callback is complete perhaps?
No, I don't think so and this approach has not mentioned in Angular DOCs

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.

how to call function when angular watch cycle or digest cycle is completed

Is there any way to call custom function in angular after angular finishes all watch cycle.
Requirement
I have multiple watch functions inside my controller. Now I want to execute a function only after all watch functions are executed by angular
There are couple of ways to do register a callback once a digest is completed.
Using $$postDigest:
$scope.$$postDigest fires a callback after the current $digest cycle completed.
However this runs only once after the next digest cycle. To make it run after each digest cycle run it along with $watch. This is based on the code sample given here
var hasRegistered = false;
$scope.$watch(function() {
if (hasRegistered) return;
hasRegistered = true;
$scope.$$postDigest(function() {
hasRegistered = false;
fn();
});
});
The $watch can get triggered multiple times during a digest cycle so we use a flag hasRegistered to prevent $$postDigest callback to be registered multiple times.
Note: $$postDigest will not trigger another digest cycle. So any modifcation to $scope inside $$postDigest will not get reflected in the dom. $$ means this is a private function in angularjs, so the function is not stable and may change in the future.
Using $timeout:
$timeout(function(){
console.log("Running after the digest cycle");
},0,false);
This runs after the current digest cycle is complete.
Note: The third argument is set to false to prevent another digest cycle trigger.

Assuring $digest Is called Following non Angular Code Execution

I have a UI component which has a $watch callback on its width (the reason is not relevant for this post).
The problem is that in some cases:
The width is changed from a non angular context ->
There is no $digest cycle ->
My $watch callback is not called.
Eventhough my application is a full angular application there are still cases in which code is executed in non angular context. For example:
JQuery calles window.setTimeout - so even if my code called JQuery from within angular context the timeout callback is called in non angular context and my $watch callback will not be executed afterwards.
By the way, even angular themselves call window.setTimeout in their AnimatorService...
So my question is:
How can I make sure a $digest cycle is always performed after any code is executed? (even when the code is a 3rd party code...)
I thought about overriding the original window.setTimeout method but:
It feels a bit ugly and dangerous.
I'm afraid it won't cover all use cases.
Adding a plunker.
The plunker sample contains:
An element which can be hidden using JQuery fadeOut method.
A button which executes the fadeOut call for hiding the element.
A text showing the element display status (Shown!!! or Hidden!!!). This text is updated by $watching on the element display property.
A button which does nothing but to initiate some angular code so that a $digest cycle is called.
Flow:
Click the Fade Out button -> the element will be hidden but the status text will remain Shown!!!.
You can wait forever now - or:
Click the Do Nothing button -> suddenly the text will change.
Why?
When clicking the Fade Out button JQuery.fadeOut calls the window.setTimeout method. After that my $watch callback is called but the element is still not hidden.
The element is only hidden after the timeout callback is called - but then there is no $digest cycle (and i have no way that i know of to trigger one).
Only on the next time an angular code will run my $watch function will be called again and the status will be updated.
AngularJS provides a special $apply method on the scope object to allow you to execute code from outside the AngularJS framework.
The $apply function will execute your code in the correct context and apply a $digest cycle afterwards so you don't have to deal with that yourself.
To implement it in your code, you can:
// Get element
var $element = $('#yourElement');
// Get scope
var scope = angular.element($element).scope();
// Execute your code
scope.$apply(function(scope){
// Your logic here
// All watchers in the scope will be triggered
});
(The scenario above can change depending on your actual application).
You can read more about the $apply method of the scope object right here: http://docs.angularjs.org/api/ng.$rootScope.Scope
Hope that helps!
Looking at your plunker, you could add a callback on the call to animate to manually trigger an update to the Angular scope once the animation is complete:
Before:
$scope.fadeOut = function() {
animatedElement.fadeOut('slow');
};
var getDisplay = function() {
return animatedElement.css('display');
};
$scope.$watch(getDisplay, function(display) {
console.log('$watch called with display = `' + display + '`');
$scope.display = display === 'none' ? 'Hidden!!!' : 'Shown!!!';
});
After:
$scope.fadeOut = function() {
animatedElement.fadeOut('slow', function() { $scope.$digest(); });
};
This would cause your watch on getDisplay to be called when the animation is complete, by which time it will return the correct value.

AngularJS : Prevent error $digest already in progress when calling $scope.$apply()

I'm finding that I need to update my page to my scope manually more and more since building an application in angular.
The only way I know of to do this is to call $apply() from the scope of my controllers and directives. The problem with this is that it keeps throwing an error to the console that reads :
Error: $digest already in progress
Does anyone know how to avoid this error or achieve the same thing but in a different way?
From a recent discussion with the Angular guys on this very topic: For future-proofing reasons, you should not use $$phase
When pressed for the "right" way to do it, the answer is currently
$timeout(function() {
// anything you want can go here and will safely be run on the next digest.
})
I recently ran into this when writing angular services to wrap the facebook, google, and twitter APIs which, to varying degrees, have callbacks handed in.
Here's an example from within a service. (For the sake of brevity, the rest of the service -- that set up variables, injected $timeout etc. -- has been left off.)
window.gapi.client.load('oauth2', 'v2', function() {
var request = window.gapi.client.oauth2.userinfo.get();
request.execute(function(response) {
// This happens outside of angular land, so wrap it in a timeout
// with an implied apply and blammo, we're in action.
$timeout(function() {
if(typeof(response['error']) !== 'undefined'){
// If the google api sent us an error, reject the promise.
deferred.reject(response);
}else{
// Resolve the promise with the whole response if ok.
deferred.resolve(response);
}
});
});
});
Note that the delay argument for $timeout is optional and will default to 0 if left unset ($timeout calls $browser.defer which defaults to 0 if delay isn't set)
A little non-intuitive, but that's the answer from the guys writing Angular, so it's good enough for me!
Don't use this pattern - This will end up causing more errors than it solves. Even though you think it fixed something, it didn't.
You can check if a $digest is already in progress by checking $scope.$$phase.
if(!$scope.$$phase) {
//$digest or $apply
}
$scope.$$phase will return "$digest" or "$apply" if a $digest or $apply is in progress. I believe the difference between these states is that $digest will process the watches of the current scope and its children, and $apply will process the watchers of all scopes.
To #dnc253's point, if you find yourself calling $digest or $apply frequently, you may be doing it wrong. I generally find I need to digest when I need to update the scope's state as a result of a DOM event firing outside the reach of Angular. For example, when a twitter bootstrap modal becomes hidden. Sometimes the DOM event fires when a $digest is in progress, sometimes not. That's why I use this check.
I would love to know a better way if anyone knows one.
From comments:
by #anddoutoi
angular.js Anti Patterns
Don't do if (!$scope.$$phase) $scope.$apply(), it means your $scope.$apply() isn't high enough in the call stack.
The digest cycle is a synchronous call. It won't yield control to the browser's event loop until it is done. There are a few ways to deal with this. The easiest way to deal with this is to use the built in $timeout, and a second way is if you are using underscore or lodash (and you should be), call the following:
$timeout(function(){
//any code in here will automatically have an apply run afterwards
});
or if you have lodash:
_.defer(function(){$scope.$apply();});
We tried several workarounds, and we hated injecting $rootScope into all of our controllers, directives, and even some factories. So, the $timeout and _.defer have been our favorite so far. These methods successfully tell angular to wait until the next animation loop, which will guarantee that the current scope.$apply is over.
Many of the answers here contain good advices but can also lead to confusion. Simply using $timeout is not the best nor the right solution.
Also, be sure to read that if you are concerned by performances or scalability.
Things you should know
$$phase is private to the framework and there are good reasons for that.
$timeout(callback) will wait until the current digest cycle (if any) is done, then execute the callback, then run at the end a full $apply.
$timeout(callback, delay, false) will do the same (with an optional delay before executing the callback), but will not fire an $apply (third argument) which saves performances if you didn't modify your Angular model ($scope).
$scope.$apply(callback) invokes, among other things, $rootScope.$digest, which means it will redigest the root scope of the application and all of its children, even if you're within an isolated scope.
$scope.$digest() will simply sync its model to the view, but will not digest its parents scope, which can save a lot of performances when working on an isolated part of your HTML with an isolated scope (from a directive mostly). $digest does not take a callback: you execute the code, then digest.
$scope.$evalAsync(callback) has been introduced with angularjs 1.2, and will probably solve most of your troubles. Please refer to the last paragraph to learn more about it.
if you get the $digest already in progress error, then your architecture is wrong: either you don't need to redigest your scope, or you should not be in charge of that (see below).
How to structure your code
When you get that error, you're trying to digest your scope while it's already in progress: since you don't know the state of your scope at that point, you're not in charge of dealing with its digestion.
function editModel() {
$scope.someVar = someVal;
/* Do not apply your scope here since we don't know if that
function is called synchronously from Angular or from an
asynchronous code */
}
// Processed by Angular, for instance called by a ng-click directive
$scope.applyModelSynchronously = function() {
// No need to digest
editModel();
}
// Any kind of asynchronous code, for instance a server request
callServer(function() {
/* That code is not watched nor digested by Angular, thus we
can safely $apply it */
$scope.$apply(editModel);
});
And if you know what you're doing and working on an isolated small directive while part of a big Angular application, you could prefer $digest instead over $apply to save performances.
Update since Angularjs 1.2
A new, powerful method has been added to any $scope: $evalAsync. Basically, it will execute its callback within the current digest cycle if one is occurring, otherwise a new digest cycle will start executing the callback.
That is still not as good as a $scope.$digest if you really know that you only need to synchronize an isolated part of your HTML (since a new $apply will be triggered if none is in progress), but this is the best solution when you are executing a function which you cannot know it if will be executed synchronously or not, for instance after fetching a resource potentially cached: sometimes this will require an async call to a server, otherwise the resource will be locally fetched synchronously.
In these cases and all the others where you had a !$scope.$$phase, be sure to use $scope.$evalAsync( callback )
Handy little helper method to keep this process DRY:
function safeApply(scope, fn) {
(scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn);
}
I had the same problem with third parties scripts like CodeMirror for example and Krpano,
and even using safeApply methods mentioned here haven't solved the error for me.
But what do has solved it is using $timeout service (don't forget to inject it first).
Thus, something like:
$timeout(function() {
// run my code safely here
})
and if inside your code you are using
this
perhaps because it's inside a factory directive's controller or just need some kind of binding, then you would do something like:
.factory('myClass', [
'$timeout',
function($timeout) {
var myClass = function() {};
myClass.prototype.surprise = function() {
// Do something suprising! :D
};
myClass.prototype.beAmazing = function() {
// Here 'this' referes to the current instance of myClass
$timeout(angular.bind(this, function() {
// Run my code safely here and this is not undefined but
// the same as outside of this anonymous function
this.surprise();
}));
}
return new myClass();
}]
)
See http://docs.angularjs.org/error/$rootScope:inprog
The problem arises when you have a call to $apply that is sometimes run asynchronously outside of Angular code (when $apply should be used) and sometimes synchronously inside Angular code (which causes the $digest already in progress error).
This may happen, for example, when you have a library that asynchronously fetches items from a server and caches them. The first time an item is requested, it will be retrieved asynchronously so as not to block code execution. The second time, however, the item is already in cache so it can be retrieved synchronously.
The way to prevent this error is to ensure that the code that calls $apply is run asynchronously. This can be done by running your code inside a call to $timeout with the delay set to 0 (which is the default). However, calling your code inside $timeout removes the necessity to call $apply, because $timeout will trigger another $digest cycle on its own, which will, in turn, do all the necessary updating, etc.
Solution
In short, instead of doing this:
... your controller code...
$http.get('some/url', function(data){
$scope.$apply(function(){
$scope.mydate = data.mydata;
});
});
... more of your controller code...
do this:
... your controller code...
$http.get('some/url', function(data){
$timeout(function(){
$scope.mydate = data.mydata;
});
});
... more of your controller code...
Only call $apply when you know the code running it will always be run outside of Angular code (e.g. your call to $apply will happen inside a callback that is called by code outside of your Angular code).
Unless someone is aware of some impactful disadvantage to using $timeout over $apply, I don't see why you couldn't always use $timeout (with zero delay) instead of $apply, as it will do approximately the same thing.
When you get this error, it basically means that it's already in the process of updating your view. You really shouldn't need to call $apply() within your controller. If your view isn't updating as you would expect, and then you get this error after calling $apply(), it most likely means you're not updating the the model correctly. If you post some specifics, we could figure out the core problem.
The shortest form of safe $apply is:
$timeout(angular.noop)
You can also use evalAsync. It will run sometime after digest has finished!
scope.evalAsync(function(scope){
//use the scope...
});
First of all, don’t fix it this way
if ( ! $scope.$$phase) {
$scope.$apply();
}
It does not make sense because $phase is just a boolean flag for the $digest cycle, so your $apply() sometimes won’t run. And remember it’s a bad practice.
Instead, use $timeout
$timeout(function(){
// Any code in here will automatically have an $scope.apply() run afterwards
$scope.myvar = newValue;
// And it just works!
});
If you are using underscore or lodash, you can use defer():
_.defer(function(){
$scope.$apply();
});
Sometimes you will still get errors if you use this way (https://stackoverflow.com/a/12859093/801426).
Try this:
if(! $rootScope.$root.$$phase) {
...
You should use $evalAsync or $timeout according to the context.
This is a link with a good explanation:
http://www.bennadel.com/blog/2605-scope-evalasync-vs-timeout-in-angularjs.htm
try using
$scope.applyAsync(function() {
// your code
});
instead of
if(!$scope.$$phase) {
//$digest or $apply
}
$applyAsync Schedule the invocation of $apply to occur at a later time. This can be used to queue up multiple expressions which need to be evaluated in the same digest.
NOTE: Within the $digest, $applyAsync() will only flush if the current scope is the $rootScope. This means that if you call $digest on a child scope, it will not implicitly flush the $applyAsync() queue.
Exmaple:
$scope.$applyAsync(function () {
if (!authService.authenticated) {
return;
}
if (vm.file !== null) {
loadService.setState(SignWizardStates.SIGN);
} else {
loadService.setState(SignWizardStates.UPLOAD_FILE);
}
});
References:
1.Scope.$applyAsync() vs. Scope.$evalAsync() in AngularJS 1.3
AngularJs Docs
I would advise you to use a custom event rather than triggering a digest cycle.
I've come to find that broadcasting custom events and registering listeners for this events is a good solution for triggering an action you wish to occur whether or not you are in a digest cycle.
By creating a custom event you are also being more efficient with your code because you are only triggering listeners subscribed to said event and NOT triggering all watches bound to the scope as you would if you invoked scope.$apply.
$scope.$on('customEventName', function (optionalCustomEventArguments) {
//TODO: Respond to event
});
$scope.$broadcast('customEventName', optionalCustomEventArguments);
yearofmoo did a great job at creating a reusable $safeApply function for us :
https://github.com/yearofmoo/AngularJS-Scope.SafeApply
Usage :
//use by itself
$scope.$safeApply();
//tell it which scope to update
$scope.$safeApply($scope);
$scope.$safeApply($anotherScope);
//pass in an update function that gets called when the digest is going on...
$scope.$safeApply(function() {
});
//pass in both a scope and a function
$scope.$safeApply($anotherScope,function() {
});
//call it on the rootScope
$rootScope.$safeApply();
$rootScope.$safeApply($rootScope);
$rootScope.$safeApply($scope);
$rootScope.$safeApply($scope, fn);
$rootScope.$safeApply(fn);
I have been able to solve this problem by calling $eval instead of $apply in places where I know that the $digest function will be running.
According to the docs, $apply basically does this:
function $apply(expr) {
try {
return $eval(expr);
} catch (e) {
$exceptionHandler(e);
} finally {
$root.$digest();
}
}
In my case, an ng-click changes a variable within a scope, and a $watch on that variable changes other variables which have to be $applied. This last step causes the error "digest already in progress".
By replacing $apply with $eval inside the watch expression the scope variables get updated as expected.
Therefore, it appears that if digest is going to be running anyways because of some other change within Angular, $eval'ing is all you need to do.
use $scope.$$phase || $scope.$apply(); instead
Understanding that the Angular documents call checking the $$phase an anti-pattern, I tried to get $timeout and _.defer to work.
The timeout and deferred methods create a flash of unparsed {{myVar}} content in the dom like a FOUT. For me this was not acceptable. It leaves me without much to be told dogmatically that something is a hack, and not have a suitable alternative.
The only thing that works every time is:
if(scope.$$phase !== '$digest'){ scope.$digest() }.
I don't understand the danger of this method, or why it's described as a hack by people in the comments and the angular team. The command seems precise and easy to read:
"Do the digest unless one is already happening"
In CoffeeScript it's even prettier:
scope.$digest() unless scope.$$phase is '$digest'
What's the issue with this? Is there an alternative that won't create a FOUT? $safeApply looks fine but uses the $$phase inspection method, too.
This is my utils service:
angular.module('myApp', []).service('Utils', function Utils($timeout) {
var Super = this;
this.doWhenReady = function(scope, callback, args) {
if(!scope.$$phase) {
if (args instanceof Array)
callback.apply(scope, Array.prototype.slice.call(args))
else
callback();
}
else {
$timeout(function() {
Super.doWhenReady(scope, callback, args);
}, 250);
}
};
});
and this is an example for it's usage:
angular.module('myApp').controller('MyCtrl', function ($scope, Utils) {
$scope.foo = function() {
// some code here . . .
};
Utils.doWhenReady($scope, $scope.foo);
$scope.fooWithParams = function(p1, p2) {
// some code here . . .
};
Utils.doWhenReady($scope, $scope.fooWithParams, ['value1', 'value2']);
};
I have been using this method and it seems to work perfectly fine. This just waits for the time the cycle has finished and then triggers apply(). Simply call the function apply(<your scope>) from anywhere you want.
function apply(scope) {
if (!scope.$$phase && !scope.$root.$$phase) {
scope.$apply();
console.log("Scope Apply Done !!");
}
else {
console.log("Scheduling Apply after 200ms digest cycle already in progress");
setTimeout(function() {
apply(scope)
}, 200);
}
}
When I disabled debugger , the error is not happening anymore. In my case, it was because of debugger stopping the code execution.
similar to answers above but this has worked faithfully for me...
in a service add:
//sometimes you need to refresh scope, use this to prevent conflict
this.applyAsNeeded = function (scope) {
if (!scope.$$phase) {
scope.$apply();
}
};
The issue is basically coming when, we are requesting to angular to run the digest cycle even though its in process which is creating issue to angular to understanding. consequence exception in console.
1. It does not have any sense to call scope.$apply() inside the $timeout function because internally it does the same.
2. The code goes with vanilla JavaScript function because its native not angular angular defined i.e. setTimeout
3. To do that you can make use of
if(!scope.$$phase){
scope.$evalAsync(function(){
});
}
let $timeoutPromise = null;
$timeout.cancel($timeoutPromise);
$timeoutPromise = $timeout(() => {
$scope.$digest();
}, 0, false);
Here is good solution to avoid this error and avoid $apply
you can combine this with debounce(0) if calling based on external event. Above is the 'debounce' we are using, and full example of code
.factory('debounce', [
'$timeout',
function ($timeout) {
return function (func, wait, apply) {
// apply default is true for $timeout
if (apply !== false) {
apply = true;
}
var promise;
return function () {
var cntx = this,
args = arguments;
$timeout.cancel(promise);
promise = $timeout(function () {
return func.apply(cntx, args);
}, wait, apply);
return promise;
};
};
}
])
and the code itself to listen some event and call $digest only on $scope you need
let $timeoutPromise = null;
let $update = debounce(function () {
$timeout.cancel($timeoutPromise);
$timeoutPromise = $timeout(() => {
$scope.$digest();
}, 0, false);
}, 0, false);
let $unwatchModelChanges = $scope.$root.$on('updatePropertiesInspector', function () {
$update();
});
$scope.$on('$destroy', () => {
$timeout.cancel($update);
$timeout.cancel($timeoutPromise);
$unwatchModelChanges();
});
You can use $timeout to prevent the error.
$timeout(function () {
var scope = angular.element($("#myController")).scope();
scope.myMethod();
scope.$scope();
}, 1);
Found this: https://coderwall.com/p/ngisma where Nathan Walker (near bottom of page) suggests a decorator in $rootScope to create func 'safeApply', code:
yourAwesomeModule.config([
'$provide', function($provide) {
return $provide.decorator('$rootScope', [
'$delegate', function($delegate) {
$delegate.safeApply = function(fn) {
var phase = $delegate.$$phase;
if (phase === "$apply" || phase === "$digest") {
if (fn && typeof fn === 'function') {
fn();
}
} else {
$delegate.$apply(fn);
}
};
return $delegate;
}
]);
}
]);
This will be solve your problem:
if(!$scope.$$phase) {
//TODO
}

Resources