When you just got started doing Angular you probably had a couple of times where you used
setTimeout() without giving it much thought. Then, you noticed that something wasn’t working right. Changes weren’t happening when you expected them to happen.
If you’re lucky you had someone nearby that just said “Oh, you should use
$timeout”. If you weren’t lucky you probably wasted 30 minutes until you found that out.
But I don’t like just knowing to use that. You should understand why you use
Let’s up your Angular game and understand exactly what are the differences between these two.
Note: everything here goes the same for
The basic difference: the digest cycle
Angular’s binding magic works by having a digest cycle mechanism. Whenever a change is made Angular goes over all the values that are bound and checks which have changed. It then updates all dependent values. Simple yet awesome.
The problem is that to be aware of when changes happen, i.e. when to run the digest cycle, Angular needs to know when asynchronous things happen. That includes user interaction, AJAX requests and, yes, timeouts.
This is why you use
ng-click and not
$http and not
$.ajax(). They make sure to trigger a digest after those asynchronous events were handled.
Same goes for
$timeout. That’s why if you have a
setTimeout function that changes your view you often don’t see the changes rendered until sometime later (when something happens to trigger a digest).
The benefits of $timeout integration
setTimeout in code you’d want to test is shooting yourself in the foot. You will have to either write very ugly asynchronous tests that depend on delays or monkey patch the global
$timeout is tightly integrated into the whole testing suite and ngMock. This means that whenever you’re testing code with
$timeouts you know it won’t be executed until you specifically call
$timeout.flush(). That will then call all pending timeouts synchronously. I know this just got you excited a bit.
$timeout returns a promise that resolves once the function has finished running (e.g. show a modal). This way if you ever need to do something once a timeout has run you don’t need to construct a promise yourself:
1 2 3 4 5
The performance angle
$timeout triggers a digest, if it happens frequently it might cause performance issues. Sometimes, though rarely, the majority of your timeouts don’t change anything related to Angular. In those cases triggering a digest is a waste.
If you google you might find outdated “tricks”. Those will tell you that performance is exactly the use case for whipping out
setTimeout. But, Angular’s
$timeout has a third argument, called
invokeApply. You can use it to… you guessed it… prevent
$apply from running and avoid the digest.
So even if you ever happen to need to squeeze this a bit to make your app performant you can do something like this:
1 2 3 4 5 6 7
Another use case is to use
invokeApply in order to have a timeout that only triggers a digest on a sub-scope and not the whole scope hierarchy. This is very advanced voodoo, but you can see an example of that here.
tl;dr Use $timeout always
Yes, we have come full circle to the exact same knowledge you had before, but now you have understanding. Knowledge is power my friend!