I'm new to Angular but really enjoying its approach. I have an HTML file where I am initializing a variable with ng-init
in a <div>
element, where I'm also declaring a controller with the ng-controller
directive:
<div ng-controller="myCtrl" ng-init='foo="bar"'>
If I console.log
the $scope
object from the controller script I can see the foo
property listed among the others, but when I try to access it from the same script it gives me undefined
. I'm also using Batarang and it shows me a model for the <div>
-scope that also includes the foo
property.
I know from the second answer to Pass variables to AngularJS controller, best practice? that I can solve the problem by moving my ng-init
directive into an outer <div>
, but I would like to know what is really going on here behind the scenes. Any help greatly appreciated, thanks in advance.
The order of the directives in the div
element does not matter. The problem is still there even if ng-init
is specified before ng-controller
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:
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):
applyDirectivesToNode
f block I declare a non-initialized nodeHasInitData
local vardirectiveName
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 nodenodeHasInitData
to true
. nothing is done if the test fails (-> nodeHasInitData
remains undefined
in the closure)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
)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
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
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({});
the controller gets created first and then foo=bar is set.
what this means is that in the code of your controller, foo doesnt exist yet. however when you are debugging, it has already been created.
here is a way around it, but i think it is really a hack. Why is the variable set in ng-init undefined in $scope in AngularJS?
imho the angular way to fix this is not put data into the view. Data belongs in your model or controller or service. This is to keep separation of concerns valid
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