从指令访问控制器范围

时间:2013-10-15 15:46:57

标签: angularjs isolate-scope

我创建了一个简单的指令,显示我正在创建的<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函数..但是我被卡住了!到目前为止,我能够做到这一点的唯一方法是为每个<sort-column>添加“onSort”的属性并引用指令中的那些:

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

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

5 个答案:

答案 0 :(得分:23)

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

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 :(得分:20)

  

&amp; local scope属性允许指令的使用者传入指令可以调用的函数。

Illustration of & scope property

详见here

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

答案 2 :(得分:8)

在您的指令中需要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/

答案 3 :(得分:2)

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

在列中,您可以创建范围变量属性:

<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
}

在“link:”功能中添加$ watch:

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

这种方法的优点IMO是你的指令完全解耦,你不需要从指令回调onSort,因为在执行路径的那一部分你并没有真正在控制器中保留onSort。 / p>

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

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

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

$scope.$emit('_sortFinished');

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

答案 4 :(得分:1)

叫我疯了,但是通过内置方法从元素中获取控制器似乎更容易,而不是摆弄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