I'm playing around with Angularjs, and possibly I am abusing it. I am using semicolon to have several statements in an angular expression like this (jsFiddle):
<tr ng-repeat="i in [1, 2, 3, 4]">
<td>i = {{ m = k; k = j; j = i + 1; i}}</td>
<td>j = {{j}}</td>
<td>k = {{k}}</td>
<td>m = {{m}}</td>
</tr>
At first, I thought that k would have the value of j before the computation i+1, but apparently it does not work like this. The result is:
i = 1 j = 2 k = 2 m = 2
i = 2 j = 3 k = 3 m = 3
i = 3 j = 4 k = 4 m = 4
i = 4 j = 5 k = 5 m = 5
So apparently assigning jto k and k to m, doesn't mean values are copied but that these names are bound together. I can understand that. But something strange happens if I remove the line that display the value of k (jsFiddle):
<tr ng-repeat="i in [1, 2, 3, 4]">
<td>i = {{ m = k; k = j; j = i + 1; i}}</td>
<td>j = {{j}}</td>
<td>m = {{m}}</td>
</tr>
I'm obtaining:
i = 1 j = 2 m =
i = 2 j = 3 m =
i = 3 j = 4 m =
i = 4 j = 5 m =
That is, m does not contain any value, despite the fact that it is bound to j (through k). It's possible it's because k itself is not evaluated.
My question is: isn't it a bug in AngularJS? Surely k should be evaluated if it's in the chain of bindings even though it is not displayed directly. Or do I misunderstand something?
I am aware that it is probably not an idiomatic way of using AngularJS, but I want to really understand the expression engine, and I can not explain this behaviour.
There are a couple of issues here that are interacting.
First, your statements are ordered backwards: you're setting k=j before setting j, which leads to its being undefined.
Second, and more importantly, interpolated expressions (the ones in "{{}}") should not be used change the state of the scope. There's a good reason for this:
The way interpolation works is that when it compiles your html, angular registers a scope.$watch on each interpolated expression.
But these watched expressions can get executed multiple times during a digest: whenever a listener modifies the scope, it causes angular to run through the watches on that scope again. For this reason, watched expressions really ought to be "idempotent": i.e., they should have no side effects / cause no state changes. Here it is from the documentation for $watch:
The watchExpression is called on every call to $digest() and should return the value which will be watched. (Since $digest() reruns when it detects changes the watchExpression can execute multiple times per $digest() and should be idempotent.)
Specifically, here's what's going on in your example. First of all, it has nothing to do with the repeater. Each ng-repeat item gets its own scope, so what's going on here is equivalent to this simpler example:
<div ng-app>
{{ i = 42 }}<br>
i = {{ m = k; k = j; j = i+1; i }}<br>
j = {{j}}<br>
k = {{k}}<br>
m = {{m}}<br>
</div>
(Here's the fiddle)
The digest proceeds like so:
{{ i = 42 }} sets scope.i to 42, and displays "42" in the document. However, since the scope has changed, there's ALSO a "dirty" flag set, which means we'll be looping through the watches again (see step 5 below){{ m = k; k = j; j = i+1; i }} sets scope.m and scope.k to undefined, and scope.j to 43, and displays "42" in the document. {{ j }} displays "43" in the document. {{ k }} and then {{ m }} both simply display "" in the document, since they're currently undefined.{{ m = k; k = j; j = i+1; i }}, k = j works because j exists; so k is assigned a value of 43. {{ k }} this time, its value has changed from undefined to 2, so the dirty flag is set once again, but now the document is displaying "k = 2". However, {{ m }} is still undefined.{{ m = k; k = j; j = i+1; i }} results in m being set to 43. Tangentially, an interesting thing I encountered while tracing through the digest code: it seems that the dirty flag is ALWAYS set to true the first time through the watches, because a never-before-checked watch has no recorded "last" value. Seems like this would result in lots of unnecessary double-processing. This seems true even when the watched value is a constant like {{2}}. Am I misunderstanding something here?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With