如何将参数传递给没有 over-writing parent 范围的指令?

How does one pass an argument to a directive without over-writing parent scope?

我需要创建一个指令,该指令作用于 table 单元格,其中 table 行使用 ng-repeat 呈现 - 为此,我部分依赖于 this answer to a question entitled "Calling a function when ng-repeat has finished". Unlike that Q&A however, I need to pass in an argument to my directive, and for this I have relied in part on this answer(标题为"Angularjs - Pass argument to directive"的问题)。

所以在我的例子中,我为我的指令添加了 fixed-column-tooltip,并且 columnselector 作为它对 <tr> 的参数,如下所示:

<tr fixed-column-tooltip columnselector=".td-keyField" ng-repeat="trData in trDataWatch">

但是当根据第二个答案时,我将我学到的是 "isolate scope" 添加到我的指令中,我不再能够访问第一个答案所需的原始范围:

'use strict';

angular.module('cmt.cases.directives')

.directive('fixedColumnTooltip', function ($timeout) {
    return {
        restrict: 'A',
        scope: {
            columnselector: '@'
        },
        link: function (scope, element, attr) {
            if (scope.$last === true) { //undefined because not operating on original scope
        ...

有没有办法保持对原始作用域的访问,同时还能访问 columnselector 参数?

尽管我对 Angular 几乎是全新的,但我正在回答我自己的问题,但仍然需要其他答案,以防我解决问题的方式不被考虑 "idiomatic" Angular。

具体来说,我没有使用隔离作用域,而是在下面的代码中利用了第三个 attrs(属性)link/function 参数,以其他方式将新的 columnselector 属性获取到html 以及我的指令。这是普遍接受的做法吗?

'use strict';

angular.module('cmt.cases.directives')

.directive('fixedColumnTooltip', function ($timeout) {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            if (scope.$last === true) {
                $timeout(function () {
                    element.parent().find(attrs.columnselector).each(function() {
                        var td = $(this);
                        var span = td.find('span');

                        if (span.width() > td.width()){
                            span.attr('data-toggle','tooltip');
                            span.attr('data-placement','right');
                            span.attr('title', span.html());
                        }
                    });
                });
            }
        }
    }
});

附录: 正如您从评论中看到的那样,尽管尝试了几种不同的方法,但我无法使 中的代码正常工作。如果我在合并该答案方面做错了什么,请告诉我。

与此同时,我找到了另一种方法,但这几乎可以肯定是 "code smell" 而不是利用 attrs 论点。具体来说,我发现我可以使用隔离范围,然后访问该范围的 $parent 范围属性。然后我会按如下方式开始我的代码,但我并不是在提倡这一点,而是只是注意到它,因为看起来 TMTOWTDI(有不止一种方法可以做到这一点)肯定适用于 Angular:

'use strict';

angular.module('cmt.cases.directives')

.directive('fixedColumnTooltip', function ($timeout) {
    return {
        restrict: 'A',
        scope: {
            columnselector: '@'
        },
        link: function (scope, element, attrs) {
            if (scope.$parent.$last === true) {
                $timeout(function () {
                    element.parent().find(scope.columnselector).each(function() {
                    ...

你可以使用,

'use strict';

angular.module('cmt.cases.directives')

.directive('fixedColumnTooltip', function ($timeout) {
    return {
        restrict: 'A',
        scope: {
            columnselector: '@',
            $last: '=$last',
        },
        link: function (scope, element, attr) {
            if (scope.$last === true) {
            ....

作用域的第二个参数将通过引用传递 $last 参数。

编辑:

由于 $last 仅在 repeat 元素范围内可用,您可以从元素范围中获取它,如下所示

'use strict';

angular.module('cmt.cases.directives')

.directive('fixedColumnTooltip', function ($timeout) {
return {
    srestrict: 'A',
    scope: {
        columnselector: '@',
    },
    link: function (scope, element, attrs) {
      var elemScope = element.scope();
      if (elemScope.$last){
             ......
      }          
    }
}

好的,首先,仅仅因为您使用的是隔离作用域并不意味着您不能访问父作用域中的某些内容。隔离范围旨在限制您默认获得的内容,但您可以从父范围指定任何您想要的内容。正确的方法是使用 "parentScopeVariable: '='" 在指令中设置双向绑定。请原谅我在移动设备上的可怕格式,我想去睡觉 :-)。

所以是的,正如您所说,您当然也可以使用 "attrs" 参数。甚至还有一些棘手的 $eval 方法可以在仅作为 attrs 传入的父作用域上设置内容。无论如何,在给定的 element/component 上,您不能有多个带有隔离范围的指令,因此在使用隔离范围时确实需要小心。不过,它绝对适合简洁的设计,因为您必须慎重考虑在指令中使用的内容。重点是,在我看来,有时依赖 attrs 是好的而且是必要的。不可否认,它确实感觉有点老套或其他什么(想想代码味道),但我认为没有充分的理由。

最后,我在 Angular API 文档站点上花了很多时间,那里有很多好东西。 $compile 服务页面上有一个很好的指令参考。再次,移动,抱歉。如果我在一台完整的计算机上,我会做很好的代码块和 link 指令参考,抱歉 :-)。快速 google 即可找到。

所以你绝对可以使用隔离作用域,并且有一些方法可以将函数回调传递回指令,将指令函数引用从指令传递回控制器,两种方式 data-binding,等等。 Isolate scope 对所有这些都很好,但它听起来不像你试图做任何太复杂的事情。

在 Angular 框架的 HTML 模板中,您可以访问父作用域..

例如:

<div ng-model="$parent.$parent.theModel"></div>

当您在模板中创建新范围时,这会起作用,例如 ng-repeat 等。理论上,您可以使用它来访问您希望使用的父范围。

可能有点难看,但有效: 获取当前指令的 DOM 元素,向后遍历其父元素,使其成为 angular-element,对其调用内置的 scope() 函数,例如

link: function (scope, elem) {
  var parentScope = angular.element ($(elem).parent()).scope();
  console.log (parentScope)
}

接触父范围可能不是最好的主意(我的意思是这不是访问不同层的 angular 方式),最好有一些额外的 scope.models。无论如何,这是一个简单的工作演示。

angular.module('app', [])
.controller('ctrl', function($scope){
  $scope.trDataWatch = ['item1', 'item2', 'item3'];
  $scope.state = 'unrendered';
  $scope.$on('ngRepeatFinished', function(){
     $scope.state = 'ngRepeatFinished';
  });
})
.directive('fixedColumnTooltip', function ($timeout) {
    return {
        restrict: 'A',
        scope: {
          columnselector: '@',
          first: '=?',
          middle: '=?',
          last: '=?',
          index: '=?',
          odd: '=?',
          even: '=?',
          
        },
        link: function (scope, element, attr) {
          if(scope.last){
              scope.$emit('ngRepeatFinished');
          }
        }
    };
});
td {
  display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
  <h4>{{state}}</h4>
  
<table>
  <tr fixed-column-tooltip columnselector=".td-keyField"
      ng-repeat="trData in trDataWatch"
      index="$index"
      odd="$odd"
      even="$even"
      first="$first"
      middle="$middle"
      last="$last">
    <td>{{trData}}</td>
  </tr>
</table>
</div>

但我强烈建议您重新设计逻辑

据我了解,您只想为 td 显示 span-tooltips 更宽,您绝对应该使用另一个指令,在第二个指令中,需要第一个指令,以便您可以使用它控制器逻辑,或其他任何东西。无论如何 - 更好的设计会更好地帮助你,所以你最好更深入地思考

如果你想在指令中使用控制器范围,你应该执行以下操作

app.directive('fixedColumnTooltip', function ($timeout) {
return {
    restrict: 'A',

    link: function (scope, element, attr) {
      var columnselector = attr.columnselector;
      console.log(scope[columnselector]);
    }
}});

这不会为指令创建任何范围,您仍然可以访问列选择器的值。如果您想在列选择器中传递函数,那么您可以执行 $parse(attr.columnselector)。如果它是一个值,则不需要 $parse。

当您在指令中定义范围时,您正在创建一个隔离范围。传递 $last 变量的最简单方法是作为另一个属性:

<tr fixed-column-tooltip columnselector=".td-keyField" ng-repeat="trData in trDataWatch" last="$last">

您的指令范围如下所示:

scope: {
    columnselector: '@',
    $last: '=last'
}

或者您可以简单地在 link 函数中访问父作用域:

link: function (scope, element, attr) {
    if (scope.$parent.$last === true) { // Will evaluate true one time
    }
}

在这种情况下,您不需要其他属性,也不需要在指令范围内定义 $last。 JSFiddle

简单。在 link 函数上使用属性参数...

link: function(scope, element, attributes, ctrl) {
  var selector = attributes.columnselector;
}

我不知道为什么我要阅读大量答案,认真的伙计们。