Como chamar um método definido em uma diretiva AngularJS?

Eu tenho uma diretiva, aqui está o código:

.directive('map', function() { return { restrict: 'E', replace: true, template: '
', 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); } } })

Eu gostaria de chamar updateMap() em uma ação do usuário. O botão de ação não está na diretiva.

Qual é a melhor maneira de chamar updateMap() de um controlador?

Se você deseja usar escopos isolados, pode passar um object de controle usando binding bidirecional = de uma variável do escopo do controlador. Você também pode controlar várias instâncias da mesma diretiva em uma página com o mesmo object de controle.

 angular.module('directiveControlDemo', []) .controller('MainCtrl', function($scope) { $scope.focusinControl = {}; }) .directive('focusin', function factory() { return { restrict: 'E', replace: true, template: '
A:{{internalControl}}
', scope: { control: '=' }, link: function(scope, element, attrs) { scope.internalControl = scope.control || {}; scope.internalControl.takenTablets = 0; scope.internalControl.takeTablet = function() { scope.internalControl.takenTablets += 1; } } }; });
  

In controller scope: {{focusinControl}}

In directive scope:

Without control object:

Supondo que o botão de ação use o mesmo controlador $scope como a diretiva, apenas defina a function updateMap no $scope dentro da function de link. Seu controlador pode então chamar essa function quando o botão de ação é clicado.

 
 app.directive('map', function() { return { restrict: 'E', replace: true, template: '
', link: function($scope, element, attrs) { $scope.updateMap = function() { alert('inside updateMap()'); } } } });

violino


Conforme o comentário de @ FlorianF, se a diretiva usa um escopo isolado, as coisas são mais complicadas. Aqui está uma maneira de fazer isso funcionar: adicione um atributo set-fn à diretiva map que registrará a function diretiva com o controlador:

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

violino

Embora seja tentador expor um object no escopo isolado de uma diretiva para facilitar a comunicação com ele, fazer isso pode levar a um código confuso de “espaguete”, especialmente se você precisar encadear essa comunicação por meio de alguns níveis (controlador, diretivo, para diretiva aninhada, etc.)

Inicialmente, percorremos esse caminho, mas depois de mais pesquisas descobrimos que fazia mais sentido e resultava em código mais legível e de fácil manutenção para expor events e propriedades que uma diretiva usaria para comunicação através de um serviço, usando $ watch nas propriedades desse serviço. a diretiva ou quaisquer outros controles que precisem reagir a essas mudanças para comunicação.

Essa abstração funciona muito bem com a estrutura de injeção de dependência do AngularJS, já que você pode injetar o serviço em qualquer item que precise reagir a esses events. Se você olhar o arquivo Angular.js, verá que as diretivas ali também usam serviços e $ watch dessa maneira, eles não expõem events sobre o escopo isolado.

Por fim, no caso de você precisar se comunicar entre diretivas dependentes uma da outra, recomendo compartilhar um controlador entre essas diretivas como meio de comunicação.

O Wiki de AngularJS para Melhores Práticas também menciona isto:

Use $. Broadcast (),. $ Emit () e. $ On () para events atômicos Eventos que são relevantes globalmente em todo o aplicativo (como uma autenticação do usuário ou o fechamento do aplicativo). Se você deseja events específicos para módulos, serviços ou widgets, considere os Serviços, os Controladores da Diretiva ou os Relatórios de Terceiros.

  • $ scope. $ watch () deve replace a necessidade de events
  • Injetar serviços e chamar methods diretamente também é útil para comunicação direta
  • As diretivas podem se comunicar diretamente entre si por meio de controladores diretivos

Com base na resposta de Oliver – você pode nem sempre precisar acessar os methods internos de uma diretiva, e nesses casos você provavelmente não precisará criar um object em branco e adicionar um control attr à diretiva apenas para evitar que ele cause um erro ( cannot set property 'takeTablet' of undefined ).

Você também pode querer usar o método em outros lugares dentro da diretiva.

Eu adicionaria um cheque para ter certeza de que scope.control existia e scope.control methods para ele de maneira semelhante ao padrão de módulo revelador.

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

Um pouco atrasado, mas esta é uma solução com o escopo isolado e “events” para chamar uma function na diretiva. Esta solução é inspirada por este post SO por satchmorun e adiciona um módulo e uma API.

 //Create module var MapModule = angular.module('MapModule', []); //Load dependency dynamically angular.module('app').requires.push('MapModule'); 

Crie uma API para se comunicar com a diretiva. O addUpdateEvent inclui um evento na matriz de events e o updateMap chama todas as funções do evento.

 MapModule.factory('MapApi', function () { return { events: [], addUpdateEvent: function (func) { this.events.push(func); }, updateMap: function () { this.events.forEach(function (func) { func.call(); }); } } }); 

(Talvez você precise adicionar funcionalidade para remover o evento.)

Na diretiva, configure uma referência ao MapAPI e inclua $ scope.updateMap como um evento quando MapApi.updateMap for chamado.

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

No controlador “principal”, inclua uma referência ao MapApi e apenas chame MapApi.updateMap () para atualizar o mapa.

 app.controller('mainController', function ($scope, MapApi) { $scope.updateMapButtonClick = function() { MapApi.updateMap(); }; } 

Para ser honesto, eu não estava realmente convencido com nenhuma das respostas neste tópico. Então, aqui estão minhas soluções:

Abordagem do manipulador diretivo (gerente)

Esse método é agnóstico para saber se o $scope da diretiva é compartilhado ou isolado

Uma factory para registrar as instâncias da diretiva

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

O código da diretiva, eu costumo colocar toda a lógica que não lida com o DOM dentro do controlador de diretivas. E registrando a instância do controlador dentro do nosso manipulador

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

código do modelo

 

Acesse a instância do controlador usando a factory e execute os methods expostos publicamente

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

Abordagem angular

Tirando uma folha do livro angular sobre como eles lidam com

 

usando $ parse e registrando o controlador no escopo $parent . Essa técnica não funciona em diretivas isoladas do $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 }; } }) 

Acesse-o dentro do controlador usando $scope.foo

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

Você pode especificar um atributo DOM que pode ser usado para permitir que a diretiva defina uma function no escopo pai. O escopo pai pode então chamar esse método como qualquer outro. Aqui está um plunker. E abaixo está o código relevante.

clearfn é um atributo no elemento de diretiva no qual o escopo pai pode passar uma propriedade de escopo que a diretiva pode definir para uma function que realiza o comportamento desejado.

         

Call method on directive

Apenas use o escopo $ parent para associar a function chamada à function diretiva

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

em HTML

 

Você pode dizer o nome do método para a diretiva para definir qual você deseja chamar do controlador, mas sem escopo isolado,

 angular.module("app", []) .directive("palyer", [ function() { return { restrict: "A", template:'
', 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; } 
  

Click play button to play

TESTADO Espero que isso ajude alguém.

Minha abordagem simples (pense nas tags como seu código original)

  
callfunction:"=callfunction" link : function(scope,element,attr) { scope.callfunction = function() { /// your code } }

Talvez esta não seja a melhor escolha, mas você pode fazer angular.element("#element").isolateScope() ou $("#element").isolateScope() para acessar o escopo e / ou o controlador de sua diretiva.

Como obter o controlador de uma diretiva em um controlador de página:

  1. escreva uma diretiva personalizada para obter a referência ao controlador de diretiva do elemento 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. use-o no html do controlador de página:

      
  3. Use o controlador de diretiva no controlador de página:

     vm.myDirectiveController.callSomeMethod(); 

Nota: a solução fornecida funciona apenas para controladores de diretivas de elementos (o nome da tag é usado para obter o nome da diretiva desejada).

Abaixo solução será útil quando, você está tendo controladores (pai e diretivo (isolado)) no formato ‘controller As’

alguém pode achar isso útil,

directiva:

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

controlador diretivo:

 function DirectiveController($location, dbConnection, uiUtility) { vmd.SetFitlersToDefaultValue = SetFitlersToDefaultValue; function SetFitlersToDefaultValue() { //your logic } } 

Código HTML :

   Clear //this button is from parent controller which will call directive controller function