【问题标题】:ng-init + ng-controller: strange behavior in the controller's scopeng-init + ng-controller:控制器范围内的奇怪行为
【发布时间】: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,问题仍然存在

【问题讨论】:

    标签: angularjs-scope


    【解决方案1】:

    好吧,我想我想通了

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

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

    在每个 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 不会改变
      猜你喜欢
      • 1970-01-01
      • 2016-03-05
      • 1970-01-01
      • 2017-02-02
      • 2019-05-19
      • 1970-01-01
      • 2014-02-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多