【问题标题】:Access controller scope from directive指令中的访问控制器范围
【发布时间】:2013-10-15 15:46:57
【问题描述】:

我创建了一个简单的指令,用于显示我正在创建的 <table> 的排序列标题。

ngGrid.directive("sortColumn", function() {
    return {
        restrict: "E",
        replace: true,
        transclude: true,
        scope: {
            sortby: "@",
            onsort: "="
        },
        template: "<span><a href='#' ng-click='sort()' ng-transclude></a></span>",
        link: function(scope, element, attrs) {
            scope.sort = function () {

                // I want to call CONTROLLER.onSort here, but how do I access the controller scope?...
                scope.controllerOnSort(scope.sortby);
            };
        }
    };
});

以下是一些正在创建的表头的示例:

<table id="mainGrid" ng-controller="GridCtrl>
<thead>
    <tr>
        <th><sort-column sortby="Name">Name</sort-column></th>
        <th><sort-column sortby="DateCreated">Date Created</sort-column></th>
        <th>Hi</th>
    </tr>
</thead>

因此,当单击排序列时,我想在我的网格控制器上触发 onControllerSort 函数。但我被卡住了!到目前为止,我能够做到这一点的唯一方法是为每个 &lt;sort-column&gt; 添加“onSort”属性并在指令中引用这些属性:

<sort-column onSort="controllerOnSort" sortby="Name">Name</sort-column>

但这不是很好,因为我总是想调用controllerOnSort,所以为每个指令都插入它有点难看。如何在指令中执行此操作而不需要在我的 HTML 中添加不必要的标记?如果有帮助,指令和控制器都在同一个模块中定义。

【问题讨论】:

    标签: angularjs isolate-scope


    【解决方案1】:

    创建第二个指令作为包装器:

    ngGrid.directive("columnwrapper", function() {
      return {
        restrict: "E",
        scope: {
          onsort: '='
        }
      };
    });
    

    然后你可以在外部指令中引用该函数调用一次:

    <columnwrapper onsort="controllerOnSort">
      <sort-column sortby="Name">Name</sort-column>
      <sort-column sortby="DateCreated">Date Created</sort-column>
    </columnwrapper>
    

    在“sortColumn”指令中,您可以通过调用来调用引用的函数

    scope.$parent.onsort();
    

    查看这个小提琴的工作示例:http://jsfiddle.net/wZrjQ/1/

    当然,如果您不关心硬编码的依赖关系,您也可以使用一个指令并通过

    scope.$parent.controllerOnSort():
    

    我有另一个小提琴显示这个:http://jsfiddle.net/wZrjQ/2

    此解决方案与其他答案 (https://stackoverflow.com/a/19385937/2572897) 中的解决方案具有相同的效果(在硬耦合方面具有相同的批评),但至少比该解决方案更容易一些。如果你无论如何都很难耦合,我认为引用控制器没有意义,因为它很可能一直在 $scope.$parent 可用(但要注意设置范围的其他元素)。

    不过,我会选择第一个解决方案。它添加了一些小标记,但解决了问题并保持了干净的分离。如果您使用第二个指令作为直接包装器,您还可以确定 $scope.$parent 与外部指令匹配。

    【讨论】:

    • 我更喜欢这个,因为它比另一个更简单。尽管在这种情况下,我仍然认为紧密耦合对我来说并不重要,因为我正在制作一个网格控件并且该指令仅用于网格控件......
    • 我尝试根据您的建议 1 创建一个父指令,但我似乎也遇到了问题 - 我不确定如何访问父指令的“范围”。我在这里发布了另一个问题:stackoverflow.com/questions/19405176/…
    • 虽然它在这里工作,但使用$parent 是一个糟糕的模式。它只会更深一层,因此很难重用。
    • 使用'=' 的双向绑定已经过时,使得迁移到 Angular 2+ 变得更加困难。如需更多信息,请参阅AngularJS Developer Guide - Component-based application architecture
    【解决方案2】:

    & 局部范围属性允许指令的使用者传入指令可以调用的函数。

    查看详情here

    这是一个answer to a similar question,它显示了如何从指令代码中传递回调函数中的参数。

    【讨论】:

      【解决方案3】:

      在您的指令中需要 ngController 并将链接函数修改为:

      ngGrid.directive("sortColumn", function() {
          return {
              ...
              require: "ngController",
              ...
              link: function(scope, element, attrs, ngCtrl) {
                  ...
              }
          };
      });
      

      ngCtrl 得到的是你的控制器,GridCtrl。你没有得到它的范围;您必须按照以下方式做一些事情:

      xxxx.controller("GridCtrl", function($scope, ...) {
          // add stuff to scope as usual
          $scope.xxxx = yyyy;
      
          // Define controller public API
          // NOTE: USING this NOT $scope
          this.controllerOnSort = function(...) { ... };
      });
      

      简单地从链接函数中调用它:

      ngCtrl.controllerOnSort(...);
      

      请注意,此要求将获得第一个父 ngController。如果在GridCtrl 和指令之间指定了另一个控制器,你会得到那个。

      演示原理的小提琴(使用方法访问父级 ng-controller 的指令):http://jsfiddle.net/NAfm5/1/


      人们担心这种解决方案可能会引入不必要的紧耦合。如果这确实是一个问题,可以这样解决:

      创建一个与控制器并行的指令,我们称之为master

      <table id="mainGrid" ng-controller="GridCtrl" master="controllerOnSort()">
      

      此指令引用控制器的所需方法(因此:解耦)。

      子指令(在您的情况下为sort-column)需要master 指令:

      require: "^master"
      

      使用$parse 服务,可以从主控制器的成员方法中调用指定的方法。请参阅实现此原则的更新小提琴:http://jsfiddle.net/NAfm5/3/

      【讨论】:

      • 我不确定以这种方式将指令和控制器紧密耦合是最好的主意。它肯定会起作用,但是任何时候你使用这个指令,你都需要在你的控制器上定义一个非常具体的方法。他目前的做法可以说是更好的做法。
      • 事实上,这些组件已经耦合,因为一个需要另一个才能正确运行。该指令取决于具有特定方法的控制器(任何控制器)的存在,此处为controllerOnSort()。这相当于让控制器在强类型语言中实现接口。可以使用 require: "?^ngController" 规范将父控制器设为可选。
      • 不正确,因为如果您查看他的问题,他正在通过属性传递回调。 Name 这将指令与任何控制器或范围解耦。在他的示例中,sort 方法可以命名为任何名称,而您的则需要严格命名。他的示例还允许从层次结构中的任何范围传入方法,而您的示例则查找直接父级。他的抱怨只是他必须在他的所有专栏中重复该属性。
      • 确实,这是(一种)权衡取舍的方法:获得便利,失去耦合(理论上,因为这些东西已经在逻辑上耦合)
      • 感谢您的彻底回复。 “紧密耦合”是一个有效点,尽管我不介意这种用途,因为我只希望该指令与网格控件一起使用,所以我认为它在此紧密耦合没有什么大问题案例
      【解决方案4】:

      还有另一种方法可以做到这一点,尽管鉴于我相对缺乏经验,我不能说这种解决方案的适用性。无论如何,我都会传递它,仅供参考。

      在您的专栏中,您创建一个范围变量属性:

      <sort-column data-sortby="sortby">Date Created</sort-column>
      

      然后在你的控制器中定义范围变量:

      $scope.sortby = 'DateCreated' // just a default sort here
      

      然后在控制器中添加你的排序功能:

      $scope.onSort = function(val) {
          $scope.sortby = val;
      }
      

      然后在你的标记中点击 ng-click:

      <sort-column data-sortby="sortby" ng-click="onSort('DateCreated')">Date Created</sort-column>
      

      然后在您的指令中将 sortby 属性添加到指令范围:

      scope: {
          sortby: '=' // not sure if you need
      }
      

      并在你的“链接:”函数中添加一个 $watch:

      scope.$watch('sortby', function () {
          ... your sort logic here ...
      }
      

      这种方法 IMO 的美妙之处在于您的指令完全解耦,您不需要从指令中回调 onSort,因为在执行路径的那部分期间您并没有真正将 onSort 留在控制器中。

      如果您需要告诉控制器等待排序完成,您可以在控制器中定义一个事件:

      $scope.$on("_sortFinished", function(event, message){
         ..do something...  
      });
      

      然后在你的指令中简单地发出事件然后过程就完成了:

      $scope.$emit('_sortFinished');
      

      还有其他方法可以做到这一点,这种方式会增加一些紧密耦合,因为您的控制器必须进行监听。并且您的指令必须发出特定的偶数...但这对您来说可能不是问题,因为它们无论如何都密切相关。

      【讨论】:

        【解决方案5】:

        说我疯了,但通过内置方法从元素中获取控制器似乎更容易,而不是摆弄require

        var mod = angular.module('something', []).directive('myDir', 
          function () {
            return {
              link: function (scope, element) {
                console.log(element.controller('myDir'));
              },
              controller: function () {
                this.works = function () {};
              },
              scope: {}
            }
          }
        );
        

        http://plnkr.co/edit/gY4rP0?p=preview

        【讨论】:

          猜你喜欢
          • 2016-03-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-09-09
          • 2018-08-30
          • 2016-04-26
          • 1970-01-01
          相关资源
          最近更新 更多