What's the difference? When should I use which? I am new to Angular & they look very similar to me. I would rather follow best practices from the start.
$interval executes a callback repeatedly, while $timeout simply delays the execution of a callback (doesn't repeat). So, no, they're not the same. Additionally, it should be noted that both of them are wrappers for window.setInterval and window.setTimeout respectively.
I would also like to recommend to read this great article written by John Resig on how Javascript timers work.
Here's some info extracted from djvirgen's response to a similar Reddit question:
You should always be using $timeout in Angular apps. Here's why:
It's injectable, making testing easier with ngMock.
It runs a digest to ensure your view is updated.
It is thenable (it's also a promise).
However, if you don't want a digest to run, you can simply pass false as the third argument.
I would guess $interval has similar advantages.
Related
I'm looking for at way to track down the pesky error of
apply/digest is already in progress
I'm familier with not using the anti-pattern and have check my codebase for nested apply's/digest's.
The problem lies in third-party plugins in this case FormEditor and Flatpickr. I have nested FlatPickr (with angular add-on) into a formEditor cshtml file which gives me the pesky error.
Is there a way to track the location of all the invokation of apply and/or digest that are present in my project?
Or does anyone have a solution to formEditor with flatPickr and flatpickr angular add-on?
FormEditor: https://github.com/kjac/FormEditor FlatPickr: https://github.com/chmln/flatpickr FlatPickr add-on: https://www.npmjs.com/package/angular-flatpickr
SOLUTION:
The problem was a $apply called by an eventListener which injected the apply into the running apply/digest. Used $timeout as suggested in the answer marked as correct.
The location was found by looking into the error log as suggested in comments
AngularJS automatically triggers a $digest cycle in many cases (after ng-click triggered for example) so the solution is not to find all the "apply / digest" in your code because it won't help you to prevent this error.
The right approach is to control 3rd parties calls to $apply method.
One approach can be wrapping $apply call with a safety check:
if(!$scope.$$phase) {
// place 3rd party updates to scope here
$scope.$apply();
}
The problem with this method is that sometimes you code won't be called.
The better approach will be to wrap it with $timeout 0:
$timeout(function(){
// place 3rd party updates to scope here
});
This way you merged more naturally into angular digest cycle.
Basically, I am unable to update my controller information when I listen for the $on event if I loaded my html dynamically using ng-include. Plunker example.
If you click once, you'll see the view keeps the original $scope.name. If you click again it will update.
I put a setTimeout on the broadcast to make sure the ng-include was loaded. You can set that to as long as you want, and will never be able to update the $scope on the first try (at least in my example).
Thoughts?
EDIT:
I'm using <ng-include="template"></ng-template>
As an area I can load alternate content in. If there is a better way to do this, please let me know.
setTimeout() is a function out of the control of AngularJS, so AngularJS will not automatically run a digest after the callback runs. That means, your $rootScope.$broadcast() was run, but AngularJS didn't realize that. The next time when you use $rootScope.template = '....';, a digest runs, and the view was updated to the previous run's model.
To solve the problem, you will need to manually call $scope.$apply() at the end of your setTimeout() callback, or use the Angular-wrapped version of setTimeout(), which is $timeout(), that will automatically run a digest afterwards.
Please refer to the docs for more details about digest/apply:
It works for me if you use $timeout instead of setTimeout. Which you should be using for angular applications.
$timeout(function(){
$rootScope.$broadcast('BROADCAST', param);
}, 1000);
There is definitely something wrong with your design if you are trying to do something like this though. Perhaps someone could suggest an alternate solution if you better explained what you are trying to achieve. As you cannot possibly know how long the timeout should be.
A few things:
1) First, do not define the $scope.template in the broadcast function. The ngInclude will not have a file to display until that value is set; so from it makes sense--in my mind--that the template would not be able to make changes before it and the controller are loaded.
2) You never actually apply Controller C2 to the ngInclude. You can do that like:
<ng-include src="template" ng-controller="c2"></ng-include>
Once I do these two things, the code works and updates the first time without the use of the setTimeout() at all.
Plunker
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).
I understand that typically one would just attach continuation code with a then() call and chain behaviour when using promises.
However, I want to kick off a promise-wrapped asynchronous call and then separately kick off a 3-second $timeout() so I can take a UI action, ONLY IF the original promise has not yet completed. (I anticipate that this would only happen on slow connections, mobile devices on 3G, etc.)
Given a promise, can I check whether it's complete or not without blocking or waiting?
I guess this was added in a recent version of Angular but there seems to be now an $$state object on the promise:
var deferred = $q.defer();
console.log(deferred.promise.$$state.status); // 0
deferred.resolve();
console.log(deferred.promise.$$state.status); //1
As noted in the comments this is not recommended as it might break when upgrading your Angular version.
I think your best option as is, (without modifying the Angular source and submitting a pull request) is to keep a local flag for if the promise has been resolved. Reset it every time you setup the promise you're interested in and mark it as complete in the then() for the original promise. In the $timeout then() check the flag to know if the original promise has resolved yet or not.
Something like this:
var promiseCompleted = false;
promise.then(function(){promiseCompleted=true;})
$timeout(...).then(function(){if(!promiseCompleted)doStuff()})
Kris Kowal's implementation includes other methods for checking the state of the promise but it appears Angular's implementation of $q unfortunately doesn't include these.
It doesn't seem to be possible, as #shaunhusain already mentioned. But maybe it's not necessary:
// shows stuff from 3s ahead to promise completetion,
// or does and undoes it in one step if promise completes before
$q.all(promise, $timeout(doStuff, 3000)).then(undoStuff);
or maybe better:
var tooSlow = $timeout(doStuff, 3000);
promise.always(tooSlow.cancel);
I have had a similar problem where I need to check if a promise has returned. Because AngularJS's $watch function will register a change while rendering the page even if both new and old values are undefined, I have to check to see if there is any data worth storing in my external model.
It is definitely a hack but I do this:
$scope.$watch('userSelection', function() {
if(promiseObject.hasOwnProperty("$$v"){
userExportableState.selection = $scope.userSelection;
}
};
I know that $$v is an internal variable used by AngularJS, but it has been quite reliable as an indicator of a resolved promise for us. Who knows what will happen when we upgrade to AngularJS 1.2 :-/ I don't see any mention of improvements to $q in the 1.2 docs but perhaps someone will write a replacement service with a better feature set closer to Q.
I don't know your exact scenario but it is more typical to put a timeout in place immediately after making the asynchronous call (and generating the promise).
Providing the setTimeout() statement is in the same event thread as the asynchronous call, you needn't worry about the possibility of a race effect. As javascript is strictly single threaded, the promise's .then() callbacks are guaranteed to fire in a later event thread.
In my app I have a $timeout that fires every minute and schedules another $timeout. Because of this my e2e tests wait for $timeout and don't want to work at all. As I understand there is no way to say karma not to wait for certain $timeout. But is there a way to detect that code is running in karma environment and don't run this $timeout at all?
I didn't find any elegant solution. For now I decided to pass a flag in a url, parse it and disable this special $timeout depending on this flag. This solution doesn't look good but it works. I hope later angular will have some kind of e2e dsl commands for this kind of things.
From AngularJS docs:
In tests you can use $timeout.flush() to synchronously flush the queue
of deferred functions.
If you call many $timeouts, you can use the waitsFor jasmine function. Something like this:
waitsFor(function () {
$timeout.flush();
return workedThreeTimes();
}, 'should run something per minute', 1000 * 60 * 3);
(sorry for my english)