Làm thế nào để gọi một phương thức được định nghĩa trong một lệnh AngularJS?


297

Tôi có một chỉ thị, đây là mã:

.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);  
        }
    }
})

Tôi muốn kêu gọi updateMap()một hành động người dùng. Nút hành động không có trong chỉ thị.

Cách tốt nhất để gọi updateMap()từ một bộ điều khiển là gì?


11
Lưu ý nhỏ: quy ước là không sử dụng ký hiệu đô la cho 'phạm vi' trong hàm liên kết, vì phạm vi không được đưa vào mà được truyền vào như một đối số thông thường.
Noam

Câu trả lời:


369

Nếu bạn muốn sử dụng phạm vi tách biệt, bạn có thể vượt qua một đối tượng điều khiển bằng cách sử dụng liên kết hai chiều =của một biến từ phạm vi điều khiển. Bạn cũng có thể kiểm soát một số trường hợp của cùng một lệnh trên một trang có cùng đối tượng điều khiển.

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 Đây cũng là cách tôi tạo API cho các thành phần có thể sử dụng lại trong Angular.
romiem

5
Câu này sạch hơn câu trả lời được chấp nhận và +1 cho tham chiếu simpsons, nếu tôi không nhầm
Blake Miller

44
Đó là cách tôi giải quyết vấn đề tương tự. Nó hoạt động, nhưng có vẻ như một hack ... Tôi ước angular có một giải pháp tốt hơn cho việc này.
Dema

1
Tôi đang học góc cạnh, vì vậy ý ​​kiến ​​của tôi có thể không có nhiều sức nặng, nhưng tôi thấy cách tiếp cận này trực quan hơn nhiều so với câu trả lời khác và sẽ đánh dấu nó là câu trả lời đúng. Tôi đã thực hiện điều này trong ứng dụng hộp cát của mình mà không gặp rắc rối nào.
BLSully

4
Có lẽ bạn nên kiểm tra để đảm bảo scope.controltồn tại, nếu không, những nơi khác sử dụng lệnh nhưng không cần truy cập vào các phương thức của lệnh và không có controlattr sẽ bắt đầu ném lỗi về việc không thể đặt thuộc tính trênundefined
CheapSteaks

73

Giả sử rằng các nút hành động sử dụng bộ điều khiển tương tự $scopenhư các chỉ thị, chỉ cần xác định chức năng updateMaptrên $scopebên trong hàm liên kết. Bộ điều khiển của bạn sau đó có thể gọi chức năng đó khi nhấp vào nút hành động.

<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


Theo nhận xét của @ FlorianF, nếu chỉ thị sử dụng phạm vi biệt lập, mọi thứ sẽ phức tạp hơn. Đây là một cách để làm cho nó hoạt động: thêm một set-fnthuộc tính vào lệnh mapsẽ đăng ký chức năng chỉ thị với bộ điều khiển:

<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


Nếu chỉ thị có phạm vi cách ly thì sao?
Florian F

Cảm ơn! (Có thể việc gọi một hàm được xác định trong bộ điều khiển của lệnh sẽ dễ dàng hơn nhưng tôi không chắc về điều đó)
Florian F

1
Đây là cách tốt hơn nhiều nếu bạn không xử lý phạm vi bị cô lập.
Martin Frank

Câu trả lời này thực sự trả lời câu hỏi OP. Nó cũng sử dụng phạm vi tách biệt, để có phạm vi tách biệt, bạn chỉ cần thêm thuộc scopetính vào khai báo chỉ thị.
Daniel G.

35

Mặc dù có thể muốn phơi bày một đối tượng trong phạm vi bị cô lập của một lệnh để tạo điều kiện giao tiếp với nó, nhưng việc này có thể dẫn đến mã "spaghetti" khó hiểu, đặc biệt nếu bạn cần xâu chuỗi giao tiếp này thông qua một vài cấp độ (bộ điều khiển, để chỉ thị, để chỉ thị lồng nhau, v.v.)

Ban đầu chúng tôi đã đi theo con đường này nhưng sau một số nghiên cứu khác thấy rằng nó có ý nghĩa hơn và dẫn đến cả mã dễ đọc và dễ đọc hơn để phơi bày các sự kiện và thuộc tính mà một lệnh sẽ sử dụng để liên lạc qua một dịch vụ sau đó sử dụng $ watch trên các thuộc tính của dịch vụ đó chỉ thị hoặc bất kỳ điều khiển nào khác cần phản ứng với những thay đổi đó để liên lạc.

Sự trừu tượng hóa này hoạt động rất độc đáo với khung tiêm phụ thuộc của AngularJS vì bạn có thể tiêm dịch vụ vào bất kỳ mục nào cần phản ứng với các sự kiện đó. Nếu bạn xem tệp Angular.js, bạn sẽ thấy rằng các lệnh trong đó cũng sử dụng các dịch vụ và $ watch theo cách này, chúng không phơi bày các sự kiện trong phạm vi bị cô lập.

Cuối cùng, trong trường hợp bạn cần liên lạc giữa các chỉ thị phụ thuộc lẫn nhau, tôi khuyên bạn nên chia sẻ bộ điều khiển giữa các chỉ thị đó làm phương tiện giao tiếp.

Wiki cho các thực tiễn tốt nhất của AngularJS cũng đề cập đến điều này:

Chỉ sử dụng. $ Broadcast (),. $ Emit () và. $ On () cho các sự kiện nguyên tử Các sự kiện có liên quan trên toàn cầu trên toàn bộ ứng dụng (như xác thực người dùng hoặc đóng ứng dụng). Nếu bạn muốn các sự kiện cụ thể cho các mô-đun, dịch vụ hoặc widget, bạn nên xem xét Dịch vụ, Bộ điều khiển chỉ thị hoặc Libs của bên thứ 3

  • $ scope. $ watch () sẽ thay thế nhu cầu cho các sự kiện
  • Tiêm dịch vụ và phương thức gọi trực tiếp cũng hữu ích cho giao tiếp trực tiếp
  • Chỉ thị có thể liên lạc trực tiếp với nhau thông qua bộ điều khiển chỉ thị

2
Tôi đã đạt đến hai giải pháp bằng trực giác: (1) xem sự thay đổi của một biến phạm vi =, biến chứa tên phương thức và đối số. (2) hiển thị chuỗi liên kết một chiều @dưới dạng id chủ đề và để callee gửi sự kiện về chủ đề này. Bây giờ tôi thấy wiki thực hành tốt nhất. Tôi nghĩ rằng có lý do để không làm điều đó theo cách có thể. Nhưng tôi vẫn chưa rõ lắm, nó hoạt động như thế nào. Trong trường hợp của tôi, tôi đã tạo một chỉ thị tabset, tôi muốn đưa ra một switchTab(tabIndex)phương thức. Bạn có thể ví dụ nhiều hơn?
stanleyxu2005

Bạn sẽ không đưa ra một switchTab(tabIndex)phương thức, bạn sẽ chỉ liên kết với một tabIndexbiến. Trình điều khiển trang của bạn có thể có các hành động thay đổi biến đó. Bạn liên kết / chuyển biến đó vào Chỉ thị tab của bạn. Chỉ thị tab của bạn sau đó có thể xem biến đó để thay đổi và thực hiện switchTab theo ý mình. Bởi vì lệnh này quyết định khi nào / làm thế nào để kiểm soát các tab của nó dựa trên một biến. Đó không phải là công việc của một nguồn bên ngoài, nếu không, các nguồn bên ngoài đòi hỏi kiến ​​thức về hoạt động bên trong của chỉ thị, đó là điều tồi tệ.
Suamere 4/11/2016

15

Dựa trên câu trả lời của Oliver - không phải lúc nào bạn cũng cần truy cập các phương thức bên trong của một lệnh và trong những trường hợp đó, bạn có thể không muốn tạo một đối tượng trống và thêm một controlattr vào lệnh chỉ để ngăn nó ném lỗi ( cannot set property 'takeTablet' of undefined).

Bạn cũng có thể muốn sử dụng phương thức này ở những nơi khác trong chỉ thị.

Tôi sẽ thêm một kiểm tra để đảm bảo scope.controltồn tại và đặt các phương thức theo cách tương tự với mẫu mô-đun tiết lộ

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

tại chỗ, sử dụng một mô hình tiết lộ bên trong chỉ thị làm cho ý định rõ ràng hơn nhiều. tốt đẹp
JSancho

12

Thành thật mà nói, tôi đã không thực sự bị thuyết phục với bất kỳ câu trả lời trong chủ đề này. Vì vậy, đây là giải pháp của tôi:

Chỉ thị tiếp cận xử lý (quản lý)

Phương pháp này không rõ ràng cho dù chỉ thị $scopelà chia sẻ hay tách biệt

A factoryđể đăng ký các trường hợp chỉ thị

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

Mã lệnh, tôi thường đặt tất cả logic không xử lý DOM trong bộ điều khiển chỉ thị. Và đăng ký phiên bản điều khiển bên trong trình xử lý của chúng tôi

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

mã mẫu

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

Truy cập phiên bản điều khiển bằng cách sử dụng factory& chạy các phương thức được hiển thị công khai

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

Cách tiếp cận của Angular

Lấy một chiếc lá ra khỏi cuốn sách góc cạnh về cách họ đối phó

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

sử dụng $ parse và đăng ký bộ điều khiển trên $parentphạm vi. Kỹ thuật này không hoạt động trên các $scopechỉ thị bị cô lập .

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

Truy cập nó bên trong bộ điều khiển bằng cách sử dụng $scope.foo

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

"Cách tiếp cận của Angular" trông tuyệt vời! Mặc dù có một lỗi đánh máy: $scope.foonên là$scope.my_form
Daniel D

Không, đó là $scope.foovì mẫu của chúng tôi <div my-directive name="foo"></div>namegiá trị của thuộc tính là 'foo'. <formchỉ là một ví dụ về một trong những chỉ thị của góc sử dụng kỹ thuật này
Mudassir Ali

10

Hơi muộn một chút, nhưng đây là một giải pháp với phạm vi và "sự kiện" bị cô lập để gọi một hàm trong lệnh. Giải pháp này được lấy cảm hứng từ bài đăng SO này của satchmorun và thêm một mô-đun và API.

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

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

Tạo một API để giao tiếp với chỉ thị. AddUpdateEvent thêm một sự kiện vào mảng sự kiện và updateMap gọi mọi chức năng sự kiện.

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

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

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

(Có lẽ bạn phải thêm chức năng để xóa sự kiện.)

Trong chỉ thị, đặt tham chiếu đến MapAPI và thêm $ scope.updateMap làm sự kiện khi MapApi.updateMap được gọi.

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

        }
    }
});

Trong trình điều khiển "chính", thêm một tham chiếu đến MapApi và chỉ cần gọi MapApi.updateMap () để cập nhật bản đồ.

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

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

2
Đề xuất này sẽ cần thêm một chút công việc trong thế giới thực khi bạn có nhiều chỉ thị cùng loại tùy thuộc vào dịch vụ API của bạn. Bạn chắc chắn sẽ gặp phải tình huống bạn cần nhắm mục tiêu và gọi các hàm từ chỉ một lệnh cụ thể và không phải tất cả chúng. Bạn có muốn nâng cao câu trả lời của bạn với một giải pháp cho vấn đề này?
smajl

5

Bạn có thể chỉ định một thuộc tính DOM có thể được sử dụng để cho phép lệnh xác định hàm trên phạm vi cha. Phạm vi cha mẹ sau đó có thể gọi phương thức này như bất kỳ phương thức nào khác. Đây là một plunker. Và dưới đây là mã liên quan.

clearfn là một thuộc tính trên thành phần chỉ thị trong đó phạm vi cha có thể vượt qua thuộc tính phạm vi mà lệnh sau đó có thể được đặt thành hàm thực hiện hành vi mong muốn.

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

Tôi không hiểu tại sao điều này hoạt động .. có phải vì thuộc tính rõ ràng nằm trong phạm vi không?
Quinn Wilson

1
Nó trở thành một phần của phạm vi chỉ thị ngay khi bạn khai báo (ví dụ scope: { clearFn: '=clearfn' }).
Trevor

2

Chỉ cần sử dụng scope. $ Parent để liên kết hàm được gọi với hàm directive

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'   
      };
});

trong HTML

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

2

Bạn có thể nói tên phương thức để chỉ thị để xác định bạn muốn gọi từ bộ điều khiển nhưng không tách biệt phạm vi,

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

THỬ NGHIỆM Hy vọng điều này sẽ giúp ai đó.

Cách tiếp cận đơn giản của tôi (Hãy nghĩ các thẻ như mã gốc của bạn)

<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

Có thể đây không phải là lựa chọn tốt nhất, nhưng bạn có thể làm angular.element("#element").isolateScope()hoặc $("#element").isolateScope()truy cập vào phạm vi và / hoặc bộ điều khiển của lệnh của bạn.


0

Cách nhận bộ điều khiển của lệnh trong bộ điều khiển trang:

  1. viết một lệnh tùy chỉnh để có được tham chiếu đến bộ điều khiển lệnh từ phần tử 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. sử dụng nó trong html của trình điều khiển trang:

    <my-directive controller="vm.myDirectiveController"></my-directive>
  3. Sử dụng bộ điều khiển chỉ thị trong bộ điều khiển trang:

    vm.myDirectiveController.callSomeMethod();

Lưu ý: giải pháp đã cho chỉ hoạt động đối với bộ điều khiển của chỉ thị phần tử (tên thẻ được sử dụng để lấy tên của lệnh được mong muốn).


0

Giải pháp bên dưới sẽ hữu ích khi bạn có các bộ điều khiển (cả cha và chỉ thị (bị cô lập)) ở định dạng 'bộ điều khiển As'

ai đó có thể thấy điều này hữu ích,

chỉ thị:

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

Kiểm soát viên chỉ thị:

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

function SetFitlersToDefaultValue() {
           //your logic
        }
}

Mã 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.