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 $timeout
.
Let’s up your Angular game and understand exactly what are the differences between these two.
Note: everything here goes the same for setInterval
and $interval
.
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 element.on('click')
or $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
Unit testing
Using 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 setTimeout
function.
Angular’s $timeout
is tightly integrated into the whole testing suite and ngMock. This means that whenever you’re testing code with $timeout
s 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.
Promises
$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
Since $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!
“Maintaining AngularJS feels like Cobol 🤷…”
You want to do AngularJS the right way.
Yet every blog post you see makes it look like your codebase is obsolete.
Components? Lifecycle hooks? Controllers are dead?
It would be great to work on a modern codebase again, but who has weeks for a rewrite?
Well, you can get your app back in shape, without pushing back all your deadlines!
Imagine, upgrading smoothly along your regular tasks, no longer deep in legacy.
Subscribe and get my free email course with steps for upgrading your AngularJS app to the latest 1.6 safely and without a rewrite.