ng-init + ng-controller:控制器范围内的奇怪行为 [英] ng-init + ng-controller: strange behavior in the controller's scope

查看:28
本文介绍了ng-init + ng-controller:控制器范围内的奇怪行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是 Angular 的新手,但真的很喜欢它的方法.我有一个 HTML 文件,我在 <div> 元素中使用 ng-init 初始化一个变量,我也在其中声明了一个带有 的控制器ng-controller 指令:

如果我 console.log 控制器脚本中的 $scope 对象,我可以看到列出的 foo 属性,但是当我尝试从它给我 undefined 的同一个脚本访问它.我也在使用 Batarang 它向我展示了<div>-scope 的模型,还包括 foo 属性.

我从 将变量传递给 AngularJS 的第二个答案中知道控制器,最佳实践? 我可以通过将我的 ng-init 指令移动到外部

来解决问题,但我想知道幕后到底发生了什么.非常感谢任何帮助,提前致谢.

编辑

div 元素中指令的顺序无关紧要.即使在ng-controller

之前指定了ng-init,问题仍然存在

解决方案

好吧,我想我明白了

ng-init 在外部/内部 els 中的不同行为是由于 Angular 执行其编译阶段的方式而产生的.编译由不同的步骤组成.在这种情况下最相关的是:

  1. 控制器实例化
  2. 预链接
  3. 链接
  4. 后链接

在每个 DOM 节点的基础上按此顺序发生(即对于每个节点,控制器代码(如果存在)在任何预链接、链接或后链接 f 之前执行)

ng-init 在指定的节点上注册一个 pre-link f,其中 $eval 是指令的内容(在在我的示例中, f 为 foo 道具分配了一个值).所以,当执行同一个节点的控制器代码时,prop还不存在,这符合@Aron的回答

在编译阶段,Angular 在深度优先的基础上从根向下遍历 DOM,这意味着父 els 在其子级之前编译.将 ng-init 指令放在外部 el 中允许子节点的控制器继承外部的作用域.这解释了 'outer el' hack

hack @Aron 指向在 prop 上注册一个观察者,这样,当 prop 在预链接阶段最终$eval被使用时,回调 f 可以找到它

我建议基于异步 JS 和 Angular 特性的另外两种可能的 hack(参见 this jsFiddle).一种涉及使用 setTimeout JS 原生 f,而另一种则更Angular"并使用 $evalAsync

恕我直言,与声明的意图相关的 ng-init 指令的 Angular 实现存在缺陷.我已经破解了 Angular 的代码来试验不同的实现.这并不难(添加了 2 行代码,甚至在可能删除 ng-init 指令本机代码之前),并且在应用于上面 jsFiddle 中的代码时可以工作,但我还没有对其进行测试复杂的应用程序.对于那些感兴趣的人,这是我正在做的事情(参考 v 1.2.0-rc2):

  • applyDirectivesToNode f 块中,我声明了一个未初始化的 nodeHasInitData 本地变量
  • 在同一个 f 中,在本地 directiveName 变量被分配了 directive.name 属性值后,我针对 "ngInit" 静态字符串,这是 Angular 在节点上声明时分配给 ng-init 指令的规范化名称
  • 如果测试通过,我将 nodeHasInitData 设置为 true.如果测试失败,则什么都不做(-> nodeHasInitData 在闭包中保持 undefined )
  • nodeLinkFn f 块中,在检查节点中控制器是否存在的 if 块之前(上面列表中的第 1 步),我添加对 nodeHasInitData 值的测试(我可以这样做,因为 nodeLinkFn 是在 applyDirectivesToNode 中定义的)
  • 如果测试通过,我调用 scope.$eval(attrs.ngInit),这就是原生 ng-init 指令的预链接 f 所做的.scopeattrs 都是 nodeLinkFn 的原生参数,所以它们是可用的.如果测试失败,什么都不做
  • 通过这种方式,我将1初始化从第 2 步移到了新的第 0 步,它在执行相应控制器的代码之前提供给内部 el 作用域

<子>1. 其实我已经复制过了,因为ng-init指令定义的prelink f还在.然而,这不是什么大问题,我认为可以通过更改/删除指令对象轻松避免

编辑

为了避免重复,为了上述黑客的说明目的,将 ngInitDirective Angular var 的赋值代码替换为 var ngInitDirective = valueFn({}) 是安全的;

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.

EDIT

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:

  1. controller instantiation
  2. prelinking
  3. linking
  4. 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 $evals 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 $evaluated 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({});

这篇关于ng-init + ng-controller:控制器范围内的奇怪行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆