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


297

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

.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()从控制器呼叫的最佳方法是什么?


11
小注释:约定不是在链接函数中对“作用域”使用美元符号,因为作用域不是注入的而是作为常规参数传递的。
2015年

Answers:


369

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

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>


11
+1这也是我为Angular中的可重用组件创建API的方式。
romiem 2013年

5
这是清洁比接受的答案,并+1辛普森参考,如果我没有记错的话
布雷克·米勒

44
那正是我解决相同问题的方式。它可以工作,但是看起来像个hack……我希望angular有一个更好的解决方案。
Dema 2013年

1
我正在学习有角度的知识,所以我的观点可能不太重要,但是我发现这种方法比其他答案更直观,并且可以将其标记为正确答案。我在沙盒应用程序中实现了零麻烦。
BLSully 2014年

4
您可能应该进行检查以确保scope.control存在,否则使用该指令但不需要访问该指令的方法且没有controlattr的其他地方将开始抛出关于无法设置属性的错误undefined
CheapSteaks

73

假设动作按钮使用相同的控制器$scope的指令,只是定义功能updateMap$scope的链接功能里面。单击操作按钮后,您的控制器即可调用该功能。

<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-fnmap指令添加属性,该属性将向控制器注册指令功能:

<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


如果指令具有隔离范围怎么办?
Florian F

谢谢!(也许调用指令的控制器中定义的函数会更容易,但我不确定)
Florian F

1
如果您不处理孤立的范围,这是更好的方法。
马丁·弗兰克

这个答案实际上回答了OP问题。它也使用隔离范围,为了拥有隔离范围,您只需要将scope属性添加到指令声明中即可。
Daniel G.

35

尽管可能很容易在指令的隔离范围内公开对象以促进与对象的通信,但这样做可能会导致“意大利面条”代码混乱,尤其是当您需要通过几个级别(控制器,指令,嵌套指令等)

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

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

最后,在需要相互依赖的指令之间进行通信的情况下,我建议在这些指令之间共享一个控制器作为通信手段。

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

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

  • $ scope。$ watch()应该代替事件的需要
  • 直接注入服务和调用方法对于直接通信也很有用
  • 指令能够通过指令控制器直接相互通信

2
我直观地达到了两种解决方案:(1)观察范围变量的变化=,该变量包含方法名称和参数。(2)将单向绑定字符串@作为主题ID 公开,并让被叫方发送与此主题相关的事件。现在,我看到了最佳实践Wiki。我认为有理由不这样做。但是我仍然不太清楚它是如何工作的。就我而言,我创建了一个tabset指令,我想公开一个switchTab(tabIndex)方法。你能举更多的例子吗?
stanleyxu2005

您不会公开switchTab(tabIndex)方法,只会绑定到tabIndex变量。您的页面控制器可能具有更改该变量的操作。您将该变量绑定/传递到选项卡“指令”中。然后,您的选项卡Directive可以监视该变量的更改,并自行执行switchTab。因为指令根据变量决定何时/如何控制其选项卡。那不是外部资源的工作,否则外部资源需要了解该指令的内部工作原理,这很不好。
Suamere

15

基于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
        };
      }
    }
  };
});

使用指令内部的显示模式可以使意图更加清晰。好一个!
JSancho 2015年

12

老实说,我对这个线程中的任何答案并没有真正的说服力。因此,这是我的解决方案:

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

该方法与指令$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的方法

从Angle的书中拿出关于它们如何处理的内容

<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
Daniel D

不,这是$scope.foo因为我们的模板是<div my-directive name="foo"></div>并且name属性的值为'foo'。<form只是采用该技术的角度指令之一的示例
Mudassir Ali

10

有点晚了,但这是一个隔离范围和“事件”的解决方案,可以在指令中调用函数。该解决方案的灵感来自satchmorun这篇SO帖子,并添加了一个模块和一个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();    
    };
}

2
当您有多个相同类型的指令(取决于您的API服务)时,该建议在现实世界中将需要做更多的工作。您肯定会遇到一种情况,仅需要从一个特定的指令而不是所有指令中定向和调用函数。您想通过解决方案来增强答案吗?
smajl

5

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

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属性在范围内有一些作用吗?
奎因·威尔逊

1
声明它(例如scope: { clearFn: '=clearfn' })后,它立即成为该指令范围的一部分。
特雷弗

2

只需使用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>

2

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

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>


1

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

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

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

0

也许这不是最佳选择,但是您可以执行angular.element("#element").isolateScope()$("#element").isolateScope()访问指令的作用域和/或控制器。


0

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

  1. 编写一个自定义指令,以从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);
        }
    }
  2. 在页面控制器的html中使用它:

    <my-directive controller="vm.myDirectiveController"></my-directive>
  3. 在页面控制器中使用指令控制器:

    vm.myDirectiveController.callSomeMethod();

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


0

当您使用“ controller As”格式的控制器(父级和指令(隔离))时,以下解决方案将非常有用

有人会觉得这很有用,

指令:

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.