1

我有一个指令,它创建一个允许用户执行搜索的 UI。该指令包装内容,这些内容将被嵌入并成为每个单独搜索结果的模板。像这样的东西:

<search>
  <div class="someStyle" ng-click="selectResult(result)">{{result.Name}}</div>
</search>

我希望 ng-click 在控制器的范围内调用 selectResult 函数,但让result 对象来自指令。如何使用指令中的隔离范围来完成此操作?

4

3 回答 3

3

您可以构建自己的搜索 transclude 指令,而不是使用 ng-transclude ,该指令可用于将结果放入 transcluded 范围。例如,使用 ng-repeat 和 search-transclude 指令,您的搜索指令可能看起来像这样,您希望在其中嵌入内容:

.directive("search", function (SearchResults) {
    return {
        restrict: "AE",
        transclude: true,
        scope: {},
        template: '<div ng-repeat="result in results">Search Relevance:' +
        '{{result.relevance}}' +
        //the most important part search-transclude that receives the current
        //result of ng-repeat
        '<div search-transclude result="result"></div></div>',
        link: function (scope, elem, attrs) {
            //get search results
            scope.results = SearchResults.results;
        }
    }
})

构建搜索嵌入指令如下:

.directive("searchTransclude", function () {
    return {
        restrict: "A",
        link: function (scope, elem, attrs, ctrl, $transclude) {
            //create a new scope that inherits from the parent of the
            //search directive ($parent.$parent) so that result can be used with other
            //items within that scope (e.g. selectResult)
            var newScope = scope.$parent.$parent.$new();
            //put result from isolate to be available to transcluded content
            newScope.result = scope.$eval(attrs.result);
            $transclude(newScope, function (clone) {
                elem.append(clone);
            });
        }
    }
})

如果它存在于创建搜索指令的范围内,则被嵌入的内容现在将能够看到 selectResult 函数。这里的例子。

于 2014-12-03T19:49:52.433 回答
1

嵌入的内容将始终使用指令元素所在的范围,即您的控制器范围。这就是为什么如果您希望函数的result参数从隔离范围获取它的值,那么您需要在隔离范围和控制器范围的属性selectResult之间建立双向绑定。result在隔离范围内将属性设置result为所需值后,控制器的范围result属性将更新为相同的值。因此,嵌入的内容将使用result与隔离范围同步的控制器result

1)resultAttr='result'向指令元素添加属性。

<search resultAttr='result'> <div class="someStyle" ng-click="selectResult(result)">{{result.Name}}</div> </search>

result2)在指令中定义隔离范围时为属性建立双向绑定:

scope: { result: "=resultAttr" }

3)result在指令中设置为某个值

于 2014-12-03T19:15:44.113 回答
1

我希望 ng-click [in the directive] 在控制器的范围内调用 selectResult 函数...

  1. 要将函数(或属性)传递到隔离范围,请使用指令标记上的属性。

...但结果对象来自指令 [范围]。

  1. 如果您希望指令标记的内容能够访问指令的范围,则不要使用 transclude。指定transclude: true告诉 angular NOT 允许指令标记的内容访问指令的范围 - 与您想要的相反。

要完成 #1,您可以让用户像这样指定模板:

  <div ng-controller="MainCtrl">

    <search external-func='selectResult'>
      <div class="someStyle" ng-click="selectResult(result)">{{result.Name}}</div>
    </search>

  </div>

注意,用户需要为<search>标签添加一个额外的属性。然而,该 html 可能更符合 angular 的理念,即 html 应该向开发人员提供有关 javascript 将对元素进行哪些操作的提示。

然后你像这样指定隔离范围:

      scope: {
        selectResult: '=externalFunc'
      },

要完成 #2,请不要transclude: true在指令中指定:

var app = angular.module('myApp',[]);

app.controller('MainCtrl', ['$scope', function($scope) {

  $scope.selectResult = function(result) {
    console.log("In MainCtrl: " + result.Name);
  };

}]);

app.controller('DirectiveCtrl', ['$scope', function($scope) {

  $scope.results = [ 
    {Name: "Mr. Result"},
    {Name: "Mrs. Result"}
  ]

}]);

app.directive('search', function() {

  return {
      restrict: 'E',

      scope: {
        selectResult: '=externalFunc'
      },

      template: function(element, attrs) {
      //                    ^       ^
      //                    |       |
      //    directive tag --+       +-- directive tag's attributes

        var inner_div = element.children();
        inner_div.attr('ng-repeat', 'result in results')

        //console.log("Inside template func: " + element.html());

        return element.html();  //Must return a string.  The return value replaces the innerHTML of the directive tag.
      },

      controller: 'DirectiveCtrl'
  }

}]);

如果您让用户更详细地指定他们的模板,那么 html 可以更好地记录 javascript 的作用:

<search external-func='selectResult'>
  <div class="someStyle" 
    ng-click="selectResult(result)"
    ng-repeat="result in results">{{result.Name}}
  </div>
</search>

但是如果你坚持极简的html:

<search>
  <div class="someStyle" ng-click="selectResult(result)">{{result.Name}}</div>
</search>

...然后您可以动态添加 ng-repeat 属性(如上所示),也可以将外部函数动态映射到隔离范围:

var app = angular.module('myApp',[]);

app.controller('MainCtrl', ['$scope', function($scope) {

  $scope.selectDog = function(result) {
    console.log("In MainCtrl: you clicked " + result.Name);
  };

  $scope.greet = function(result) {
    console.log('MainCtrl: ' + result.Name);
  };

}]);

app.controller('DirectiveCtrl', ['$scope', function($scope) {

  $scope.results = [ 
    {Name: "Mr. Result"},
    {Name: "Mrs. Result"}
  ]

}]);

app.directive('search', function() {

  return {
    restrict: 'E',

    scope: {
      externalFunc: '&externalFunc'  //Cannot write => externalFunc: '&'
    },                               //because the attribute name is
                                     //'external-func', which means
                                     //the left hand side would have to be external-func.
    template: function(element, attrs) {
      //Retrieve function specified by ng-click:
      var inner_div = element.children();
      var ng_click_val = inner_div.attr('ng-click'); //==>"selectResult(result)"

      //Add the outer_scope<==>inner_scope mapping to the directive tag:
      //element.attr('external', ng_click_val); //=> No worky! Angular does not create the mapping.
      //But this works:
      attrs.$set('externalFunc', ng_click_val) //=> external-func="selectResult(result)"
      //attrs.$set('external-func', ng_click_val); //=> No worky!

      //Change ng-click val to use the correct call format:
      var func_args = ng_click_val.substring(ng_click_val.indexOf('(')); //=> (result)
      func_args =  func_args.replace(/[\(]([^\)]*)[\)]/, "({$1: $1})"); //=> ({result: result})
      inner_div.attr('ng-click', 'externalFunc' + func_args); //=> ng-click="externalFunc({result: result})"

      //Dynamically add an ng-repeat attribute:
      inner_div.attr('ng-repeat', 'result in results')

      console.log("Template: " + element[0].outerHTML);
      return element.html();
    },

    controller: 'DirectiveCtrl'
  }
})

如果你想用多个参数调用外部函数,你可以这样做:

var app = angular.module('myApp',[]);

app.controller('MainCtrl', ['$scope', function($scope) {

  $scope.selectResult = function(result, index) {
    console.log("In MainCtrl: you clicked " 
                 +  result.Name 
                 + " " 
                 + index);
  };

}]);

app.controller('DirectiveCtrl', ['$scope', function($scope) {

  $scope.results = [ 
    {Name: "Mr. Result"},
    {Name: "Mrs. Result"}
  ]

}]);

app.directive('search', function() {
  return {
    restrict: 'E',

    scope: {
      external: '='
    },

    template: function(element, attrs) {
      //Extract function name specified by ng-click:
      var inner_div = element.children();
      var ng_click_val = inner_div.attr('ng-click'); //=>"selectResult(result, $index)"
      var external_func_name =  ng_click_val.substring(0, ng_click_val.indexOf('(') ); //=> selectResult
      external_func_name = external_func_name.trim();

      //Add the outer_scope<==>inner_scope mapping to the directive tag:
      //element.attr('externalFunc', ng_click_val); => No worky!
      attrs.$set('external', external_func_name);  //=> external="selectResult"

      //Change name of ng-click function to 'external':
      ng_click_val = ng_click_val.replace(/[^(]+/, 'external');
      inner_div.attr('ng-click', ng_click_val);

      //Dynamically add ng-repeat to div:
      inner_div.attr('ng-repeat', 'result in results');

      console.log("Template: " + element[0].outerHTML);
      return element.html();
    },

    controller: 'DirectiveCtrl'
  }
});
于 2014-12-20T05:42:29.703 回答