如何调用 AngularJS 指令中定义的方法?

Posted

技术标签:

【中文标题】如何调用 AngularJS 指令中定义的方法?【英文标题】:How to call a method defined in an AngularJS directive? 【发布时间】:2013-05-28 16:52:26 【问题描述】:

我有一个指令,这里是代码:

.directive('map', function() 
    return 
        restrict: 'E',
        replace: true,
        template: '<div></div>',
        link: function($scope, element, attrs) 

            var center = new google.maps.LatLng(50.1, 14.4); 
            $scope.map_options = 
                zoom: 14,
                center: center,
                mapTypeId: google.maps.MapTypeId.ROADMAP
            ;
            // create map
            var map = new google.maps.Map(document.getElementById(attrs.id), $scope.map_options);
            var dirService= new google.maps.DirectionsService();
            var dirRenderer= new google.maps.DirectionsRenderer()

            var showDirections = function(dirResult, dirStatus) 
                if (dirStatus != google.maps.DirectionsStatus.OK) 
                    alert('Directions failed: ' + dirStatus);
                    return;
                  
                  // Show directions
                dirRenderer.setMap(map);
                //$scope.dirRenderer.setPanel(Demo.dirContainer);
                dirRenderer.setDirections(dirResult);
            ;

            // Watch
            var updateMap = function()
                dirService.route($scope.dirRequest, showDirections); 
            ;    
            $scope.$watch('dirRequest.origin', updateMap);

            google.maps.event.addListener(map, 'zoom_changed', function() 
                $scope.map_options.zoom = map.getZoom();
              );

            dirService.route($scope.dirRequest, showDirections);  
        
    
)

我想就用户操作致电updateMap()。操作按钮不在指令上。

从控制器调用updateMap() 的最佳方式是什么?

【问题讨论】:

小注:惯例是不要在链接函数中使用美元符号作为“作用域”,因为作用域不是注入的,而是作为常规参数传入的。 【参考方案1】:

有点晚了,但这是一个具有隔离范围和“事件”的解决方案,可以在指令中调用函数。此解决方案的灵感来自 this SO post by satchmorun,并添加了一个模块和一个 API。

//Create module
var MapModule = angular.module('MapModule', []);

//Load dependency dynamically
angular.module('app').requires.push('MapModule');

创建一个 API 来与指令通信。 addUpdateEvent 将一个事件添加到事件数组中,updateMap 调用每个事件函数。

MapModule.factory('MapApi', function () 
    return 
        events: [],

        addUpdateEvent: function (func) 
            this.events.push(func);
        ,

        updateMap: function () 
            this.events.forEach(function (func) 
                func.call();
            );
        
    
);

(也许您必须添加功能才能删除事件。)

在指令中设置对 MapAPI 的引用,并在调用 MapApi.updateMap 时将 $scope.updateMap 作为事件添加。

app.directive('map', function () 
    return 
        restrict: 'E', 
        scope: , 
        templateUrl: '....',
        controller: function ($scope, $http, $attrs, MapApi) 

            $scope.api = MapApi;

            $scope.updateMap = function () 
                //Update the map 
            ;

            //Add event
            $scope.api.addUpdateEvent($scope.updateMap);

        
    
);

在“主”控制器中添加对 MapApi 的引用,然后调用 MapApi.updateMap() 来更新地图。

app.controller('mainController', function ($scope, MapApi) 

    $scope.updateMapButtonClick = function() 
        MapApi.updateMap();    
    ;

【讨论】:

当您有多个相同类型的指令取决于您的 API 服务时,此提案在现实世界中需要更多的工作。您肯定会遇到一种情况,您需要仅从一个特定指令而不是所有指令中定位和调用函数。您想通过解决方案来增强您的答案吗?【参考方案2】:

已测试 希望这对某人有所帮助。

我的简单方法(将标签视为您的原始代码)

<html>
<div ng-click="myfuncion"> 
<my-dir callfunction="myfunction">
</html>

<directive "my-dir">
callfunction:"=callfunction"
link : function(scope,element,attr) 
scope.callfunction = function() 
 /// your code


</directive>

【讨论】:

【参考方案3】:

当您拥有“控制器作为”格式的控制器(父级和指令(隔离))时,以下解决方案将很有用

有人可能会觉得这很有用,

指令:

var directive = 
        link: link,
        restrict: 'E',
        replace: true,
        scope: 
            clearFilters: '='
        ,
        templateUrl: "/temp.html",
        bindToController: true, 
        controller: ProjectCustomAttributesController,
        controllerAs: 'vmd'
    ;
    return directive;

    function link(scope, element, attrs) 
        scope.vmd.clearFilters = scope.vmd.SetFitlersToDefaultValue;
    

指令控制器:

function DirectiveController($location, dbConnection, uiUtility) 
  vmd.SetFitlersToDefaultValue = SetFitlersToDefaultValue;

function SetFitlersToDefaultValue() 
           //your logic
        

html代码:

      <Test-directive clear-filters="vm.ClearFilters"></Test-directive>
    <a class="pull-right" style="cursor: pointer" ng-click="vm.ClearFilters()"><u>Clear</u></a> 
//this button is from parent controller which will call directive controller function

【讨论】:

【参考方案4】:

如何在页面控制器中获取指令的控制器:

    编写自定义指令以从 DOM 元素中获取对指令控制器的引用:

    angular.module('myApp')
        .directive('controller', controller);
    
    controller.$inject = ['$parse'];
    
    function controller($parse) 
        var directive = 
            restrict: 'A',
            link: linkFunction
        ;
        return directive;
    
        function linkFunction(scope, el, attrs) 
            var directiveName = attrs.$normalize(el.prop("tagName").toLowerCase());
            var directiveController = el.controller(directiveName);
    
            var model = $parse(attrs.controller);
            model.assign(scope, directiveController);
        
    
    

    在页面控制器的 html 中使用它:

    <my-directive controller="vm.myDirectiveController"></my-directive>
    

    在页面控制器中使用指令控制器:

    vm.myDirectiveController.callSomeMethod();
    

注意:给定的解决方案仅适用于元素指令的控制器(标签名称用于获取所需指令的名称)。

【讨论】:

【参考方案5】:

如果您想使用隔离作用域,您可以使用控制器作用域中变量的双向绑定= 传递控制对象。您还可以使用相同的控件对象在页面上控制同一指令的多个实例。

angular.module('directiveControlDemo', [])

.controller('MainCtrl', function($scope) 
  $scope.focusinControl = ;
)

.directive('focusin', function factory() 
  return 
    restrict: 'E',
    replace: true,
    template: '<div>A:internalControl</div>',
    scope: 
      control: '='
    ,
    link: function(scope, element, attrs) 
      scope.internalControl = scope.control || ;
      scope.internalControl.takenTablets = 0;
      scope.internalControl.takeTablet = function() 
        scope.internalControl.takenTablets += 1;
      
    
  ;
);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="directiveControlDemo">
  <div ng-controller="MainCtrl">
    <button ng-click="focusinControl.takeTablet()">Call directive function</button>
    <p>
      <b>In controller scope:</b>
      focusinControl
    </p>
    <p>
      <b>In directive scope:</b>
      <focusin control="focusinControl"></focusin>
    </p>
    <p>
      <b>Without control object:</b>
      <focusin></focusin>
    </p>
  </div>
</div>

【讨论】:

+1 这也是我在 Angular 中为可重用组件创建 API 的方式。 这比公认的答案更干净,如果我没记错的话,可以为辛普森一家参考 +1 这正是我解决同样问题的方法。它有效,但它看起来像一个黑客......我希望 Angular 有一个更好的解决方案。 我正在学习 Angular,所以我的观点可能没有多大分量,但我发现这种方法比其他答案更直观,并将其标记为正确答案。我在我的沙盒应用程序中实现了这个,零麻烦。 您可能应该检查以确保scope.control 存在,否则其他使用该指令但不需要访问该指令的方法且没有control attr 的地方将开始抛出关于无法在undefined上设置属性的错误【参考方案6】:

说实话,我对这个帖子中的任何答案都不是很信服。所以,这是我的解决方案:

指令处理程序(管理器)方法

此方法与指令的$scope 是共享的还是隔离的无关

一个factory 来注册指令实例

angular.module('myModule').factory('MyDirectiveHandler', function() 
    var instance_map = ;
    var service = 
        registerDirective: registerDirective,
        getDirective: getDirective,
        deregisterDirective: deregisterDirective
    ;

    return service;

    function registerDirective(name, ctrl) 
        instance_map[name] = ctrl;
    

    function getDirective(name) 
        return instance_map[name];
    

    function deregisterDirective(name) 
        instance_map[name] = null;
    
);

指令代码,我通常将所有不处理DOM的逻辑放在指令控制器中。并在我们的处理程序中注册控制器实例

angular.module('myModule').directive('myDirective', function(MyDirectiveHandler) 
    var directive = 
        link: link,
        controller: controller
    ;

    return directive;

    function link() 
        //link fn code
    

    function controller($scope, $attrs) 
        var name = $attrs.name;

        this.updateMap = function() 
            //some code
        ;

        MyDirectiveHandler.registerDirective(name, this);

        $scope.$on('destroy', function() 
            MyDirectiveHandler.deregisterDirective(name);
        );
    
)

模板代码

<div my-directive name="foo"></div>

使用factory 访问控制器实例并运行公开的方法

angular.module('myModule').controller('MyController', function(MyDirectiveHandler, $scope) 
    $scope.someFn = function() 
        MyDirectiveHandler.get('foo').updateMap();
    ;
);

Angular 的方法

从 Angular 的书中学习他们如何处理

<form name="my_form"></form>

使用$parse 并在$parent 范围内注册控制器。此技术不适用于孤立的 $scope 指令。

angular.module('myModule').directive('myDirective', function($parse) 
    var directive = 
        link: link,
        controller: controller,
        scope: true
    ;

    return directive;

    function link() 
        //link fn code
    

    function controller($scope, $attrs) 
        $parse($attrs.name).assign($scope.$parent, this);

        this.updateMap = function() 
            //some code
        ;
    
)

使用$scope.foo在控制器内部访问它

angular.module('myModule').controller('MyController', function($scope) 
    $scope.someFn = function() 
        $scope.foo.updateMap();
    ;
);

【讨论】:

“Angular 的方法”看起来很棒!不过有一个错字:$scope.foo 应该是 $scope.my_form 不,应该是$scope.foo,因为我们的模板是&lt;div my-directive name="foo"&gt;&lt;/div&gt;,而name 属性的值是'foo'。 &lt;form 只是使用此技术的 angular 指令之一的示例【参考方案7】:

您可以将方法名称告诉指令以定义要从控制器调用的方法,但没有隔离范围,

angular.module("app", [])
  .directive("palyer", [
    function() 
      return 
        restrict: "A",
        template:'<div class="player"><span ng-bind="text"></span></div>',
        link: function($scope, element, attr) 
          if (attr.toPlay) 
            $scope[attr.toPlay] = function(name) 
              $scope.text = name + " playing...";
            
          
        
      ;
    
  ])
  .controller("playerController", ["$scope",
    function($scope) 
      $scope.clickPlay = function() 
        $scope.play('AR Song');
      ;
    
  ]);
.player
  border:1px solid;
  padding: 10px;
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
  <div ng-controller="playerController">
    <p>Click play button to play
      <p>
        <p palyer="" to-play="play"></p>
        <button ng-click="clickPlay()">Play</button>

  </div>
</div>

【讨论】:

【参考方案8】:

也许这不是最佳选择,但您可以通过 angular.element("#element").isolateScope()$("#element").isolateScope() 访问指令的范围和/或控制器。

【讨论】:

【参考方案9】:

只需使用 scope.$parent 将调用的函数关联到指令函数

angular.module('myApp', [])
.controller('MyCtrl',['$scope',function($scope) 

])
.directive('mydirective',function()
 function link(scope, el, attr)
   //use scope.$parent to associate the function called to directive function
   scope.$parent.myfunction = function directivefunction(parameter)
     //do something


return 
        link: link,
        restrict: 'E'   
      ;
);

在 HTML 中

<div ng-controller="MyCtrl">
    <mydirective></mydirective>
    <button ng-click="myfunction(parameter)">call()</button>
</div>

【讨论】:

【参考方案10】:

尽管在指令的隔离范围内公开对象以促进与它的通信可能很诱人,但这样做可能会导致混淆“意大利面条”代码,特别是如果您需要通过几个级别(控制器、到指令,到嵌套指令等)

我们最初沿着这条路走,但经过更多研究后发现它更有意义,并导致更易于维护和可读的代码公开事件和属性,指令将用于通过服务进行通信,然后在该服务上使用 $watch指令中的服务属性或需要对这些更改做出反应以进行通信的任何其他控件。

这种抽象与 AngularJS 的依赖注入框架配合得非常好,因为您可以将服务注入到需要对这些事件做出反应的任何项目中。如果您查看 Angular.js 文件,您会发现其中的指令也以这种方式使用服务和 $watch,它们不会在隔离范围内公开事件。

最后,如果您需要在相互依赖的指令之间进行通信,我建议在这些指令之间共享一个控制器作为通信方式。

AngularJS's Wiki for Best Practices 也提到了这一点:

仅对原子事件使用 .$broadcast()、.$emit() 和 .$on() 在整个应用程序中全局相关的事件(例如用户身份验证或应用程序关闭)。如果您想要特定于模块、服务或小部件的事件,您应该考虑服务、指令控制器或 3rd 方库

$scope.$watch() 应该取代对事件的需求 直接注入服务和调用方法对于直接通信也很有用 指令可以通过指令控制器直接相互通信

【讨论】:

我直观地达到了两个解决方案:(1)观察范围变量=的变化,该变量包含方法名称和参数。 (2) 公开一个单向绑定字符串@ 作为主题ID,并让被调用者发送关于该主题的事件。现在我看到了最佳实践维基。我认为有理由不这样做。但我仍然不是很清楚,它是如何工作的。就我而言,我创建了一个 tabset 指令,我想公开一个 switchTab(tabIndex) 方法。你能举个例子吗? 您不会公开switchTab(tabIndex) 方法,您只会绑定到tabIndex 变量。您的页面控制器可能具有更改该变量的操作。您将该变量绑定/传递到您的选项卡指令中。然后,您的 tab 指令可以观察该变量的变化,并自行执行 switchTab。因为该指令根据变量决定何时/如何控制其选项卡。这不是外部来源的工作,否则外部来源需要了解指令的内部工作原理,这很糟糕。【参考方案11】:

您可以指定一个 DOM 属性,该属性可用于允许指令在父作用域上定义一个函数。然后,父作用域可以像其他任何方法一样调用此方法。 Here's 一个笨蛋。以下是相关代码。

clearfn 是指令元素上的一个属性,父作用域可以将作用域属性传递到该属性中,然后指令可以将其设置为实现所需行为的函数。

<!DOCTYPE html>
<html ng-app="myapp">
  <head>
    <script data-require="angular.js@*" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
    <link rel="stylesheet" href="style.css" />
    <style>
      my-box
        display:block;
        border:solid 1px #aaa;
        min-width:50px;
        min-height:50px;
        padding:.5em;
        margin:1em;
        outline:0px;
        box-shadow:inset 0px 0px .4em #aaa;
      
    </style>
  </head>
  <body ng-controller="mycontroller">
    <h1>Call method on directive</h1>
    <button ng-click="clear()">Clear</button>
    <my-box clearfn="clear" contentEditable=true></my-box>
    <script>
      var app = angular.module('myapp', []);
      app.controller('mycontroller', function($scope)
      );
      app.directive('myBox', function()
        return 
          restrict: 'E',
          scope: 
            clearFn: '=clearfn'
          ,
          template: '',
          link: function(scope, element, attrs)
            element.html('Hello World!');
            scope.clearFn = function()
              element.html('');
            ;
          
        
      );
    </script>
  </body>
</html>

【讨论】:

我不明白为什么会这样。是因为 clear 属性在某些范围内吗? 一旦你声明它就成为指令范围的一部分(例如scope: clearFn: '=clearfn' )。【参考方案12】:

基于 Oliver 的回答 - 您可能并不总是需要访问指令的内部方法,在这些情况下,您可能不希望创建一个空白对象并向指令添加 control attr 以防止它不会引发错误(cannot set property 'takeTablet' of undefined)。

您可能还想在指令中的其他地方使用该方法。

我会添加一个检查以确保 scope.control 存在,并以与显示模块模式类似的方式为其设置方法

app.directive('focusin', function factory() 
  return 
    restrict: 'E',
    replace: true,
    template: '<div>A:control</div>',
    scope: 
      control: '='
    ,
    link : function (scope, element, attrs) 
      var takenTablets = 0;
      var takeTablet = function() 
        takenTablets += 1;  
      

      if (scope.control) 
        scope.control = 
          takeTablet: takeTablet
        ;
      
    
  ;
);

【讨论】:

指出,在指令中使用显示模式可以使意图更加清晰。不错!【参考方案13】:

假设动作按钮使用与指令相同的控制器$scope,只需在链接函数内部的$scope 上定义函数updateMap。然后,您的控制器可以在单击操作按钮时调用该函数。

<div ng-controller="MyCtrl">
    <map></map>
    <button ng-click="updateMap()">call updateMap()</button>
</div>
app.directive('map', function() 
    return 
        restrict: 'E',
        replace: true,
        template: '<div></div>',
        link: function($scope, element, attrs) 
            $scope.updateMap = function() 
                alert('inside updateMap()');
            
        
    
);

fiddle


根据@FlorianF 的评论,如果指令使用隔离范围,事情会更复杂。这是使其工作的一种方法:将set-fn 属性添加到map 指令,它将向控制器注册指令功能:

<map set-fn="setDirectiveFn(theDirFn)"></map>
<button ng-click="directiveFn()">call directive function</button>
scope:  setFn: '&' ,
link: function(scope, element, attrs) 
    scope.updateMap = function() 
       alert('inside updateMap()');
    
    scope.setFn(theDirFn: scope.updateMap);

function MyCtrl($scope) 
    $scope.setDirectiveFn = function(directiveFn) 
        $scope.directiveFn = directiveFn;
    ;

fiddle

【讨论】:

如果指令有一个孤立的范围怎么办? 谢谢! (也许调用指令控制器中定义的函数会更容易,但我不确定) 如果您不处理孤立的范围,这是更好的方法。 这个答案确实回答了 OP 问题。它也使用独立作用域,为了拥有一个独立作用域,您只需将scope 属性添加到指令声明中。

以上是关于如何调用 AngularJS 指令中定义的方法?的主要内容,如果未能解决你的问题,请参考以下文章

AngularJS:如何使用自定义指令来取代ng-repeat

angularJS中如何写自定义指令

angularJS指令

AngularJS 自定义服务指令

AngularJS指令$ scope未定义

angularjs如何在视图渲染结束之后,或者render之后执行指令中的link方法呢?