$digest
loop, a short explanation of assignment of values in javascript
and, finally, a way to confidently have values updated in your controllers.
So let’s say you are a mad scientist that is trying to create a living being that will commit acts of cruelty and murder driven by its loneliness and despair. Let’s also say you have set up your whole machinery and what-not, leaving you waiting for a thunderstorm that will provide enough energy through lightning to finally ‘shock’ your creature to life.
In order to be alerted when the monster becomes alive, you build a
monster service
:
1 2 3 4 5 6 7 8 9 |
|
As soon as something calls the Monster
Service with the right weather,
it will become alive.
Since you care about the status of the abomination you created, you try to check for the monsters’s aliveness and display your emotions accordingly in a controller:
1 2 3 4 |
|
The accompanying view:
1 2 3 4 5 6 7 8 9 10 11 |
|
Now since Angular.js has all this two-way binding magic you spend your time waiting with a girl you just met at Starbucks, confident of being informed once the monster becomes alive.
Unfortunately, the result is this: Once the weather changes to a thunderstorm, your monster becomes alive and you don’t notice while chatting about how great the latest Beirut album is and how no one really gets it. Your monster is free to roam and wreak havoc on mankind as we know it.
Angular.js two-way data binding (aka $digest
loop) roughly works as follows:
alive
in the view, angular creates a “watch” for the
$scope.alive
property.Now, that may be fine, but: Given that the monster has become alive, why
won’t the change be propagated up to the MadScientistCtrl
?
It turns out that the operation
1
|
|
does not make $scope.alive
reference the Monster.alive
property, but
merely ends up copying it.
Thus, although the Monster.alive
value changes, the copy in $scope.alive
does not and the angular machinery can not pick up the change.
I will go into some theoretical detail to debug the simple assignment instruction to $scope.alive
.
The error shown above is a result of misunderstanding how JavaScript
assignment works – we need to understand what primitives, objects and
assignments are to understand Variable Assignment:
A primitive is a value of type string
, number
, boolean
, null
or
undefined
. Primitives are immutable – i.e. its value cannot be
changed by invoking functions on it.
The Mozilla JavaScript Glossary defines an object as an “unordered collection of properties”, among other things. An object is mutable – you can change the values its properties reference, add new properties or delete some.
A variable is a reference to value that is either an object or a primitive. The same variable can reference different values in its lifecycle.
Every assignment of a value to a variable makes the variable reference the value.
1 2 |
|
An assignment of variable b
to variable a
makes variable
b
reference the same value that variable a
references.
1 2 3 |
|
Assigning a value y
to a variable that references a value x
does
not change the value x
– it merely makes the variable reference a
different value without losing the original one. A reference to the old
value x
will still refer to the original, unchanged value.
1 2 3 4 5 6 |
|
During this little excursion the monster has been running rampant and you consider it a lost case. Naturally you have already created the next creature to bring darkness to the world and are, again, in the position of waiting for the weather to produce a thunderstorm.
Again, you spend your time talking to a girl that you met at the vegan farmers market, explaining why you consider the renaissance 80’s sneakers to be an ironic display of non-mainstream fashion sensibility. And again, you want to be informed of the change in your monster’s aliveness. But this time, you change your routine:
1 2 3 |
|
1 2 3 4 5 6 7 8 9 10 11 |
|
Check the result here.
Obviously the experiment worked and you have been informed about the
change. But Why? Let’s consider the angular $digest
loop again and see
how this small change in the code made it work:
monster.alive
in the view, angular creates a “watch” for the
$scope.monster.alive
property.$scope.monster.alive = false
).When we assigned $scope.monster = Monster;
, we created a reference to
the Monster service (an object!) on the $scope
. Thus when comparing the old and new
values of $scope.monster.alive
, angular looks up the reference and
sees the new value in Monster.alive
. The difference in the value makes
angular re-render the UI, finally showing you as excited as you ought
to be for not only once, but twice creating a creature that is an insult
to all living things.
Of course other techniques for binding service values to controllers exist if you feel that directly referencing an object in your view is too much coupling, or that it is still necessary to bind to a primitive value somehow,
This can be achieved by adding a “manual” watch, which will update your scopes variables upon change of the watched value:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
The accompanying view:
1 2 3 4 5 6 7 8 9 10 11 |
|
$digest
loop and related concepts.$digest
cycle and is a really insightful read. Highly recommended.Images from “Young Frankenstein” and “Frankenstein” are believed to be in public domain.
]]>