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 j
to 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