ok, I think I figured it out
the different behavior of ng-init
in outer/inner els arises because of the way Angular executes its compiling phase. compiling consists of different steps. the most relevant in this case are:
- controller instantiation
- prelinking
- linking
- postlinking
that take place in this order on a per-DOMnode basis (i.e. for each node, the controller code, if present, is executed before any prelink, link, or postlink f)
ng-init
registers a pre
-link f on the node it is specified in, which $eval
s the directive's content (in my example, the f assigns a value to the foo
prop). so, when the controller code for the same node is executed, the prop does not exist yet, which is in line with @Aron's answer
in the compile phase, Angular traverses the DOM from the root down on a depth-first basis, which means that parent els are compiled before their children. putting the ng-init
directive in an outer el allows the controller of the child node to inherit the outer's scope. this explains the 'outer el' hack
the hack @Aron points to registers an observer on the prop, so that, when the prop is finally $eval
uated in the prelink phase, the callback f can find it
I suggest two other possible hacks based on asynchronous JS and Angular features (see this jsFiddle). one involves using setTimeout
JS native f, whereas the other is more 'Angular' and resorts to $evalAsync
imho, there's a flaw in Angular's implementation of the ng-init
directive with respect to the declared intent. I have hacked the Angular's code to experiment a diverse implementation. It is not difficult (2 lines of code added, even before possibly removing the ng-init
directive native code), and works when applied to the code in the jsFiddle above, but I have not tested it on complex apps. For those interested, here is what I'm doing (refs are to v 1.2.0-rc2):
- in the
applyDirectivesToNode
f block I declare a non-initialized nodeHasInitData
local var
- in the same f, after the local
directiveName
var is assigned the directive.name
prop value, I test it against the "ngInit"
static string, which is the normalized name Angular assigns to the ng-init
directive when it is declared on the node
- if the test passes, I set the
nodeHasInitData
to true
. nothing is done if the test fails (-> nodeHasInitData
remains undefined
in the closure)
- in the
nodeLinkFn
f block, before the if
block that checks for the presence of controllers in the node (step 1 in the list above), I'm adding a test on the value of nodeHasInitData
(I can do that because nodeLinkFn
is defined inside applyDirectivesToNode
)
- if the test passes, I invoke
scope.$eval(attrs.ngInit)
, which is what the prelink f of the native ng-init
directive does. both scope
and attrs
are native params of nodeLinkFn
, so they are available. nothing is done if the test fails
- this way, I have moved1 the initialization from step 2 to a new step 0, which feeds the inner el scope before the corresponding controller's code is executed
1. Actually, I have replicated it, because the prelink f defined by the ng-init
directive is still there. It is not a great deal, however, and I think it could be easily avoided by changing/removing the directive object
EDIT
To avoid replication, it is safe, for the illustrative purposes of the hack described above, to replace the assignment code of the ngInitDirective
Angular var with var ngInitDirective = valueFn({});