ng-init + ng-controller:控制器范围内的奇怪行为
Posted
技术标签:
【中文标题】ng-init + ng-controller:控制器范围内的奇怪行为【英文标题】:ng-init + ng-controller: strange behavior in the controller's scope 【发布时间】:2013-06-10 21:01:07 【问题描述】:我是 Angular 的新手,但我真的很喜欢它的方法。我有一个 html 文件,我在其中使用 <div>
元素中的 ng-init
初始化一个变量,我还使用 ng-controller
指令声明一个控制器:
<div ng-controller="myCtrl" ng-init='foo="bar"'>
如果我 console.log
控制器脚本中的 $scope
对象,我可以看到列出的 foo
属性,但是当我尝试从同一个脚本访问它时,它给了我 undefined
。我也在使用Batarang,它向我展示了<div>
-scope 的模型,其中还包括foo
属性。
我从Pass variables to AngularJS controller, best practice? 的第二个回答中知道,我可以通过将ng-init
指令移动到外部<div>
来解决问题,但我想知道幕后真正发生了什么。非常感谢任何帮助,在此先感谢。
编辑
div
元素中指令的顺序无关紧要。即使在ng-controller
之前指定了ng-init
,问题仍然存在
【问题讨论】:
【参考方案1】:好吧,我想我想通了
ng-init
在外部/内部 els 中的不同行为是由于 Angular 执行其编译阶段的方式而产生的。编译由不同的步骤组成。在这种情况下最相关的是:
-
控制器实例化
预链接
链接
后链接
在每个 DOM 节点的基础上按此顺序发生(即,对于每个节点,控制器代码(如果存在)在任何预链接、链接或后链接 f 之前执行)
ng-init
在它指定的节点上注册一个pre
-link f,其中$eval
s 指令的内容(在我的示例中,f 为foo
属性分配了一个值)。因此,当执行同一节点的控制器代码时,该道具尚不存在,这符合@Aron的答案
在编译阶段,Angular 在深度优先的基础上从根向下遍历 DOM,这意味着父 el 在其子 el 之前编译。将ng-init
指令放在外部el 中允许子节点的控制器继承外部的范围。这解释了'outer el' hack
hack @Aron 指向在 prop 上注册一个观察者,这样,当 prop 在 prelink 阶段最终被 $eval
uated 时,回调 f 可以找到它
我建议基于异步 JS 和 Angular 功能的另外两种可能的 hack(参见 this jsFiddle)。一种涉及使用setTimeout
JS native f,而另一种则更“Angular”并诉诸$evalAsync
恕我直言,关于声明的意图,Angular 对 ng-init
指令的实现存在缺陷。我已经破解了 Angular 的代码来试验不同的实现。这并不难(添加了 2 行代码,甚至在可能删除 ng-init
指令本机代码之前),并且在应用于上面 jsFiddle 中的代码时可以工作,但我还没有在复杂的应用程序上测试过它。对于那些感兴趣的人,这就是我正在做的事情(参考 v 1.2.0-rc2):
applyDirectivesToNode
f 块中我声明了一个未初始化的nodeHasInitData
local var
在同一个 f 中,在为本地 directiveName
var 分配了 directive.name
属性值之后,我针对 "ngInit"
静态字符串对其进行测试,这是 Angular 分配给 ng-init
指令时的规范化名称在节点上声明
如果测试通过,我将nodeHasInitData
设置为true
。如果测试失败,则什么也不做(-> nodeHasInitData
在闭包中仍然是 undefined
)
在nodeLinkFn
f 块中,在检查节点中是否存在控制器的if
块之前(上面列表中的第1 步),我添加了对nodeHasInitData
值的测试(我可以这样做,因为nodeLinkFn
是在applyDirectivesToNode
中定义的)
如果测试通过,我调用scope.$eval(attrs.ngInit)
,这就是本机ng-init
指令的prelink f 所做的。 scope
和 attrs
都是 nodeLinkFn
的原生参数,因此它们是可用的。如果测试失败则什么都不做
这样,我将1从第 2 步的初始化移到了新的第 0 步,它在执行相应控制器的代码之前提供内部 el 范围
1.其实我已经复制了,因为ng-init
指令定义的prelink f还在。但是,这并不是什么大问题,我认为可以通过更改/删除指令对象来轻松避免
编辑
为了避免复制,出于上述黑客的说明目的,将ngInitDirective
Angular var 的分配代码替换为var ngInitDirective = valueFn();
是安全的
【讨论】:
【参考方案2】:首先创建控制器,然后设置 foo=bar。
这意味着在你的控制器的代码中, foo 还不存在。但是当你调试的时候,它已经被创建了。
这是一种解决方法,但我认为这确实是一种 hack。 Why is the variable set in ng-init undefined in $scope in AngularJS?
恕我直言,解决此问题的角度方法不是将数据放入视图中。数据属于您的模型或控制器或服务。这是为了保持关注点分离有效
【讨论】:
感谢@Anton,这很有帮助。但是,我认为问题不在于控制器的创建,而在于控制器的执行。当控制器代码被执行时,它的$scope
还没有得到ng-init
指令的内容。我在这里发现问题出在 Angular 的代码执行流程中,另请参阅我在问题中的编辑。
你指出我的问题的答案有效,但我认为有更好的方法,我不确定这不是 Angular 实现 ng-init
指令的缺陷。毕竟documentation says说它“指定了在引导过程中模板进入执行模式之前要执行的初始化任务。”
我也明白您关于关注点分离的观点,但初始化可能会有所帮助,尤其是出于引导性能方面的原因(例如,避免另一个 HTTP 请求来检索您的应用的初始数据)
除非初始化与特定视图数据/逻辑有关,否则恕我直言,它应该属于模型或控制器。
ng-init
与 ng-model
+initialization 相当,所以我们在这里接触模型,而我不会将初始化任务分配给控制器【参考方案3】:
一种解决方法是使用 ng-init 的函数。
<div ng-app>
<div ng-controller="MyCtrl" ng-init="init('kittens')">
Page title is pageTitle
</div>
</div>
控制器:
var MyCtrl = function($scope)
$scope.init = function(pageTitle)
$scope.pageTitle = pageTitle;
;
我一直在使用 ng-init 为我的控制器指定一个类型 - 即控制器在应用程序中使用了很多次,但每次它都需要一个不同的信息。它省去了我为每种类型制作单独的控制器。
相关问题:
Why is the variable set in ng-init undefined in $scope in AngularJS?
How to set scope property with ng-init?
【讨论】:
好吧,那么当我们从表单元素更改值时,ng-model 不会改变以上是关于ng-init + ng-controller:控制器范围内的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章