如何从指令控制器调用link函数中定义的范围方法

时间:2014-10-17 12:56:50

标签: angularjs angularjs-directive angularjs-scope

Here's a Plunker - 我希望“被替换”替换为“替换”。

我的问题如下。我有两个指令myTabs和myPane在myTabs的link函数中,我在其隔离范围中添加了一个名为setValue的方法。现在,我希望myPane能够设置值,所以我向myTabs的控制器添加了一个方法setValue,它基本上调用了scope的方法。但是,由于控制器功能在链接功能之前运行,因此无法访问范围的方法。

有关完整代码,请参阅Plunker,但以下是相关部分:

指令:

angular.module('docsTabsExample', [])
.directive('myTabs', function() {
  return {
    transclude: true,
    scope: {},
    controller: function($scope) {
      this.setValue = $scope.setValue;
    },
    link: function(scope, element, attrs){
      scope.value = 'To be replaced';
      scope.setValue = function(value){
        scope.value = value;
      };
    },
    templateUrl: 'my-tabs.html'
  };
})
.directive('myPane', function() {
  return {
    require: '^myTabs',
    transclude: true,
    scope: {
    },
    link: function(scope, element, attrs, tabsCtrl) {
      tabsCtrl.setValue('Replacement');
    }
  };
});

HTML:

<body ng-app="docsTabsExample">
<my-tabs>
<my-pane>
   One pane
</my-pane>
<my-pane title="World">
   Another pane
</my-pane>
</my-tabs>
</body>

run时,您可以在控制台中看到Error: tabsCtrl.setValue is not a function

现在,为了确保我没有朝着错误的方向前进,我应该描述我的实际问题。我正在写一个require s ngModel,的指令,它有一个嵌套指令。由于我只能在链接函数中访问ngModel的控制器,所以setValue()方法只能添加到那里的范围,但是我需要控制器将API暴露给嵌套指令,这就是为什么我要尝试{ {1}}。

2 个答案:

答案 0 :(得分:1)

你真的只需要让指令控制器管理对$ scope的访问。

当创建控制器时,它将具有与指令相同的范围,因此您可以将控制器代码重新编写为如下所示:

controller: function($scope) {

 //Snip...

 this.setValue = function(value){
   $scope.value = value;
 };
}

将逻辑移动到控制器后,您可以安全地从指令中删除范围函数。 但是,您需要解决一个操作顺序问题。

默认情况下,link: function(){}是一个后置链接功能,它将执行 AFTER 所有子指令的链接。您可以通过将逻辑移动到预链接功能来解决此问题,因为它已经链接了 BEFORE 子指令。

link: {
  pre: function(scope, element, attrs){
   scope.value = 'To be replaced';
   //No need for function here
  }
}

现在你的嵌套指令可以简单地在控制器上执行方法,而控制器又会在范围上设置适当的值。没有什么需要改变。

以下是包含这些更改的Plunker:http://plnkr.co/edit/ERf24t93rm1pmr8avyV1?p=preview

更新

好的,这里有一个完整的示例,说明如何在子指令中使用ngModel以影响实际的绑定模型。您可以看到更改已同时更新<input>绑定到progressValue以及实际自定义控件。

如果你想自己玩这个,这里有一个Plunker:http://plnkr.co/edit/zdzm0l4THlCDTvdP4ne3?p=preview

&#13;
&#13;
(function() {

  var template = '<div>' +
    '<div ng-transclude></div>' +
    '<div class="progress">' +
    '<div class="progress-bar progress-bar-success progress-bar-striped"' +
    ' role="progressbar" aria-valuenow="0"' + 
    ' aria-valuemin="0" aria-valuemax="100">' +
    '<span></span>' +
    '</div>' +
    '</div>' +
    '</div>';

  function ParentWithModelDirective() {
    return {
      restrict: 'E',
      require: 'ngModel',
      template: template,
      transclude: true,
      replace: true,
      scope: {},
      link: {
        pre: function(scope, elem, attrs, ngModel) {
          var progressBar = elem.find('div').find('div'),
            label = progressBar.find('span');

          ngModel.$parsers.push(function(viewValue) {
            var numValue = 0;

            if (angular.isString(viewValue)) {
              viewValue = viewValue.replace('%', '');
            }

            numValue = parseInt(viewValue);

            return isNaN(numValue) ? 0 : numValue;
          });

          ngModel.$formatters.push(function(value) {
            return ngModel.$isEmpty(value) ? 'N/A' : value.toString() + '%';
          });

          ngModel.$render = function() {
            progressBar.css('width', ngModel.$viewValue);
            progressBar.attr('aria-valuenow', 
                             ngModel.$isEmpty(ngModel.$modelValue) ? 
                             0 : 
                             ngModel.$modelValue);
            label.text(ngModel.$viewValue);
          };
        }
      }
    };
  }

  function ChildOfParentDirective() {
    return {
      restrict: 'E',
      require: '^ngModel',
      template: "<button type='button' class='btn btn-success'>Complete</button>",
      scope: {},
      link: function(scope, elem, attrs, ngModel) {

        function listener() {
          ngModel.$setViewValue('100%', 'click');
          ngModel.$render();
        }

        elem.on('click', listener);
      }
    };
  }

  function AnotherChildOfParentDirective() {
    return {
      restrict: 'E',
      require: '^ngModel',
      template: "<button type='button' class='btn btn-warning'>Reset</button>",
      scope: {},
      link: function(scope, elem, attrs, ngModel) {
        function listener() {
          ngModel.$setViewValue('0%', 'click');
          ngModel.$render();
        }

        elem.on('click', listener);
      }
    };
  }

  angular.module('sample-app', [])
    .directive('parentWithModel', ParentWithModelDirective)
    .directive('childOfParent', ChildOfParentDirective)
    .directive('anotherChildOfParent', AnotherChildOfParentDirective)

}());
&#13;
<script src="http://code.angularjs.org/1.3.0/angular.js"></script>
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" 
      rel="stylesheet" />
<div class="container" ng-app='sample-app' ng-init="progressValue = 30">
  <div class="masthead">
    <h3 class="text-muted">Sample App
        </h3>
  </div>
  <div class="row">
    <div class="col-sm-12">

      <div class="form-group">
        <label>Progress Value</label>
        <input type="text" class="form-control" ng-model="progressValue" />
      </div>

    </div>
  </div>
  <div class="row">
    <div class="col-sm-12">
      <parent-with-model ng-model="progressValue">
        <h1><code>progressValue = {{progressValue}}</code></h1>
        <child-of-parent></child-of-parent>
        <another-child-of-parent></another-child-of-parent>
        <br />
        <br />
      </parent-with-model>
    </div>
  </div>
  <!-- Site footer -->
  <div class="footer">
    <p>by <a href="http://www.technofattie.com">Techno Fattie</a>
    </p>
  </div>
</div>
&#13;
&#13;
&#13;

答案 1 :(得分:0)

为什么ngModel上需要myTabs?如果您不能直接将setValue功能移至控制器以解决问题。