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,它向我展示了&lt;div&gt;-scope 的模型,其中还包括foo 属性。

我从Pass variables to AngularJS controller, best practice? 的第二个回答中知道,我可以通过将ng-init 指令移动到外部&lt;div&gt; 来解决问题,但我想知道幕后真正发生了什么。非常感谢任何帮助,在此先感谢。

编辑

div 元素中指令的顺序无关紧要。即使在ng-controller 之前指定了ng-init,问题仍然存在

【问题讨论】:

【参考方案1】:

好吧,我想我想通了

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

    控制器实例化 预链接 链接 后链接

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

ng-init 在它指定的节点上注册一个pre-link f,其中$evals 指令的内容(在我的示例中,f 为foo 属性分配了一个值)。因此,当执行同一节点的控制器代码时,该道具尚不存在,这符合@Aron的答案

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

hack @Aron 指向在 prop 上注册一个观察者,这样,当 prop 在 prelink 阶段最终被 $evaluated 时,回调 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 所做的。 scopeattrs 都是 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-initng-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:控制器范围内的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章

AngularJS框架基本标签

Angular指令总结

AngularJS学习笔记01 序

reactjs 中 ng-init 的等价物

在Angularjs中使用ng-init初始化包含'的值时出现错误

无法使用 ng-init 调用 angularjs 函数